docs(mosaic): preserve static focus input fixture failure evidence
This commit is contained in:
parent
5e2961d05b
commit
fb0293cd85
14 changed files with 957 additions and 0 deletions
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
### STEP20C experimental evidence
|
||||
|
||||
- Preserved static focus/input/standard fixture failure: exact static binary and loaded pair verified; one run stopped before captures/input on an incorrect border-width fixture assertion. INCONCLUSIVE / UNDECIDED; camera and clip traces retained, RGB/clicks/splith NOT TESTED, no retry or production change.
|
||||
|
||||
- Approved private static candidate decisions (2026-10-13): separate notices, project maintenance and exact-pair qualification. Current package unchanged; private real `/usr` static build/model/staging/library identity/headless startup pass. Whole-H2 reference fixture failed after a focus change; input and splith return NOT TESTED. PARTIALLY_FEASIBLE / UNDECIDED, preserved historical evidence; the original manifests and fixture bytes remain unchanged.
|
||||
|
||||
- Preserved static distribution contract: one hash-qualified static executable staged through private Meson metadata at /usr/bin/swayfx-mosaic, exact-one-file shape PASS without recompilation. Existing scene interposition is characterized, version/security/notice obligations and an unsubmitted upstream API dossier are explicit. PARTIALLY_FEASIBLE, contract decision required; no recipe, packaging contract, production or system change. Historical verdict retained; later user decisions authorize a separate private gate only.
|
||||
|
|
|
|||
|
|
@ -405,3 +405,14 @@ reference equality fails through a focus/decor mismatch; input and splith return
|
|||
are NOT TESTED after the single run stopped. No rerun or algorithm modification
|
||||
occurred. Distribution notice closure, named release/security roles and broader
|
||||
ABI/graphics gates remain open. STEP20C NEXT, multi-output NOT YET SUPPORTED.
|
||||
|
||||
The [static focus/input/standard follow-up](design/STEP20C_STATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md)
|
||||
is **EXPERIMENTAL / INCONCLUSIVE**, architecture **UNDECIDED**. One run reused
|
||||
the exact static binary, verified loaded wlroots/output geometry, calculated
|
||||
camera and observed the applied clip. A fixture incorrectly required stored
|
||||
current_border_width=0 under border none; actual IPC retained thickness 2 with
|
||||
empty decoration and full-size client rectangles. The run stopped before any
|
||||
PNG, click or splith transition. Those gates are NOT TESTED; no clipping defect
|
||||
or need for an algorithm change is demonstrated. No retry/build/production
|
||||
change occurred. A separately authorized corrected-fixture gate is required.
|
||||
STEP20C stays NEXT; production multi-output remains NOT YET SUPPORTED.
|
||||
|
|
|
|||
|
|
@ -346,3 +346,14 @@ reference equality fails through a focus/decor mismatch; input and splith return
|
|||
are NOT TESTED after the single run stopped. No rerun or algorithm modification
|
||||
occurred. Distribution notice closure, named release/security roles and broader
|
||||
ABI/graphics gates remain open. STEP20C NEXT, multi-output NOT YET SUPPORTED.
|
||||
|
||||
The [static focus/input/standard follow-up](design/STEP20C_STATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md)
|
||||
is **EXPERIMENTAL / INCONCLUSIVE**, architecture **UNDECIDED**. One run reused
|
||||
the exact static binary, verified loaded wlroots/output geometry, calculated
|
||||
camera and observed the applied clip. A fixture incorrectly required stored
|
||||
current_border_width=0 under border none; actual IPC retained thickness 2 with
|
||||
empty decoration and full-size client rectangles. The run stopped before any
|
||||
PNG, click or splith transition. Those gates are NOT TESTED; no clipping defect
|
||||
or need for an algorithm change is demonstrated. No retry/build/production
|
||||
change occurred. A separately authorized corrected-fixture gate is required.
|
||||
STEP20C stays NEXT; production multi-output remains NOT YET SUPPORTED.
|
||||
|
|
|
|||
|
|
@ -264,3 +264,14 @@ reference equality fails through a focus/decor mismatch; input and splith return
|
|||
are NOT TESTED after the single run stopped. No rerun or algorithm modification
|
||||
occurred. Distribution notice closure, named release/security roles and broader
|
||||
ABI/graphics gates remain open. STEP20C NEXT, multi-output NOT YET SUPPORTED.
|
||||
|
||||
The [static focus/input/standard follow-up](design/STEP20C_STATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md)
|
||||
is **EXPERIMENTAL / INCONCLUSIVE**, architecture **UNDECIDED**. One run reused
|
||||
the exact static binary, verified loaded wlroots/output geometry, calculated
|
||||
camera and observed the applied clip. A fixture incorrectly required stored
|
||||
current_border_width=0 under border none; actual IPC retained thickness 2 with
|
||||
empty decoration and full-size client rectangles. The run stopped before any
|
||||
PNG, click or splith transition. Those gates are NOT TESTED; no clipping defect
|
||||
or need for an algorithm change is demonstrated. No retry/build/production
|
||||
change occurred. A separately authorized corrected-fixture gate is required.
|
||||
STEP20C stays NEXT; production multi-output remains NOT YET SUPPORTED.
|
||||
|
|
|
|||
|
|
@ -350,3 +350,14 @@ reference equality fails through a focus/decor mismatch; input and splith return
|
|||
are NOT TESTED after the single run stopped. No rerun or algorithm modification
|
||||
occurred. Distribution notice closure, named release/security roles and broader
|
||||
ABI/graphics gates remain open. STEP20C NEXT, multi-output NOT YET SUPPORTED.
|
||||
|
||||
The [static focus/input/standard follow-up](design/STEP20C_STATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md)
|
||||
is **EXPERIMENTAL / INCONCLUSIVE**, architecture **UNDECIDED**. One run reused
|
||||
the exact static binary, verified loaded wlroots/output geometry, calculated
|
||||
camera and observed the applied clip. A fixture incorrectly required stored
|
||||
current_border_width=0 under border none; actual IPC retained thickness 2 with
|
||||
empty decoration and full-size client rectangles. The run stopped before any
|
||||
PNG, click or splith transition. Those gates are NOT TESTED; no clipping defect
|
||||
or need for an algorithm change is demonstrated. No retry/build/production
|
||||
change occurred. A separately authorized corrected-fixture gate is required.
|
||||
STEP20C stays NEXT; production multi-output remains NOT YET SUPPORTED.
|
||||
|
|
|
|||
320
docs/design/STEP20C_STATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md
Normal file
320
docs/design/STEP20C_STATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
# STEP20C static runtime focus, input and standard-return probe
|
||||
|
||||
**EXPERIMENTAL / INCONCLUSIVE. Historical campaign frozen as evidence.**
|
||||
Phase A published `5e2961d05b7b33252718aabc4dbf787724f79ef0` to both remotes,
|
||||
with a clean worktree. The original phase B was local and unpublished; the later evidence freeze publishes its sources and findings without replay.
|
||||
[Architecture](../ARCHITECTURE.md), [UX](../UX.md), [Packaging](../PACKAGING.md)
|
||||
and [Roadmap](../ROADMAP.md) remain authoritative. STEP20A DONE, STEP20B
|
||||
EXPERIMENTAL, STEP20C NEXT, STEP20D–G PLANNED; production multi-output
|
||||
NOT YET SUPPORTED. No production, algorithm, shader, package or UX change.
|
||||
|
||||
## Historical evidence and exact candidate
|
||||
|
||||
The [previous runtime](STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md) remains
|
||||
PARTIALLY_FEASIBLE / UNDECIDED. It captured h2-reference.png while workspace 2
|
||||
was focused, then h2-mosaic.png after workspace 1/client setup and Mosaic.
|
||||
The full RGB comparison failed by 23,807 pixels, bounding box (0,0,800,600).
|
||||
Changes outside the unchanged reference interior support focus/title/border
|
||||
variation. No target colors appeared on H2; 182,214 survived on H1. Neither
|
||||
cropping borders nor a new successful run can retroactively pass that comparison.
|
||||
|
||||
Reuse only the historical staged binary from
|
||||
`/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt/stage-static/usr/bin/swayfx-mosaic`:
|
||||
SHA-256 `f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b`,
|
||||
ELF build ID `942db64269640778ee433e5970ba1100bc60f94c`.
|
||||
Verify original/copy hash, executable regular-file mode, ELF dependencies and
|
||||
system wlroots SHA-256 `7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837`.
|
||||
No SceneFX DT_NEEDED or loaded SceneFX shared mapping is allowed. Stop before
|
||||
launch if identity is unavailable. No SceneFX/Mosaic build or binary alteration.
|
||||
|
||||
Historical C17 saved-client and static-reference plus the extent-adapted virtual
|
||||
pointer are reused from manifest-verified source. Their historical executable
|
||||
files are not entries of those manifests: rebuild only these clients from the
|
||||
verified C/generated-protocol sources, record commands and resulting hashes.
|
||||
No client algorithm changes. Target alternates two opaque colors on configure;
|
||||
reference remains RGB (48,48,56) across focus/configure changes. Pointer uses
|
||||
1600×600 logical extent and global-x + 800 normalization.
|
||||
|
||||
## Single-run protocol and stop gates
|
||||
|
||||
One new private LAB, mode-0700 runtime, explicit private Wayland display and IPC
|
||||
socket, WLR_BACKENDS=headless. Every swaymsg uses -s. Only owned process groups
|
||||
are stopped; preserve exit codes and verify no residual process/socket.
|
||||
A 150-second deadline and bounded logs apply. No pristine run, no popup.
|
||||
HEADLESS-1=(-800,0,800,600), HEADLESS-2=(0,0,800,600), scale 1, normal.
|
||||
|
||||
Create reference on workspace 2, then filler and target on workspace 1. Both
|
||||
reference and filler have org.mosaic.StaticReference App ID. Therefore do not
|
||||
use the broad for_window App ID rule: obtain reference con_id while it is the
|
||||
only reference and apply `[con_id=ID] border none` in private IPC. Check reference
|
||||
PID, workspace/output, border none and zero border width; filler keeps its normal
|
||||
border. Keep focus_follows_mouse no. No daily configuration changes.
|
||||
|
||||
Activate Mosaic, read real applied owner/tiling traces and IPC scroll. Compute
|
||||
one signed camera delta from horizontal content offset and configured width,
|
||||
then reread scene/scroll. The historical +160 is not a constant. No further
|
||||
camera command or hide/return. Focus must initially remain target/workspace 1.
|
||||
If later normal focus changes move the camera, retain that observation and stop
|
||||
any assertion needing unchanged Mosaic geometry; do not correct it.
|
||||
|
||||
PI_OWNER gives an applied scene owner, not a buffer. IPC rect/window_rect/deco_rect
|
||||
and the client's last real COMMIT size are separate evidence. Use the actual H1
|
||||
painted color rectangle to identify vertical content and pick an interior point.
|
||||
Correlate measured paint with horizontal scene offset and client width; never
|
||||
label the old source_box y estimate a native buffer coordinate. PI_SCENE local
|
||||
clip plus output-parent origin and tiled translation gives global viewport.
|
||||
|
||||
After complete Mosaic setup and sole calibration, park pointer at (-790,590),
|
||||
wait for stabilization, capture H2 twice at least 500 ms apart. Check 800×600,
|
||||
full-frame reference color, border, focus/workspace and exact whole RGB equality.
|
||||
Store PNG hashes and snapshots. No ROI, no tolerance. Failure stops before input.
|
||||
|
||||
V1: capture H1/H2, retain positive target-color count on H1, zero on H2, and
|
||||
whole-H2 equality to preflight. Require content crossing x=0 and clip ending
|
||||
there. Save every differing pixel coordinate/color on failure; stop dependent tests.
|
||||
|
||||
V2: choose a point at least 20 pixels inside the actually painted H1 rectangle,
|
||||
outside decorations and borders. One right press/release; require actual target
|
||||
BUTTON 273 states 1 then 0 with recorded serials, no reference delivery. A move
|
||||
alone is never input proof. Preserve available passive scene hit and Wayland logs.
|
||||
|
||||
V3: choose an H2 point within the geometrical continuation of the target content
|
||||
and the full-frame reference interior. One right press/release; require zero
|
||||
new target BUTTON events and two real reference events. Record focus after click,
|
||||
park pointer, stabilize and recapture whole H2 with exact equality. Do not infer
|
||||
PASS from neither client receiving input.
|
||||
|
||||
V4: address the target con_id with layout splith (after V3 global focus is H2;
|
||||
a bare layout command could affect the wrong workspace). Verify workspace identity,
|
||||
applied splith, target still mapped, applied owner and PI_SCENE clip_valid=0 on
|
||||
H1 and ordinary H2. Capture H1/H2. Choose a fresh interior target-colored point
|
||||
from the standard image, inject one click and require actual target press/release.
|
||||
Park pointer and recapture H2 after focus transition. Require stable H2 and no
|
||||
residual clip. Do not reuse the Mosaic click point blindly.
|
||||
|
||||
## Budget, analysis and classification
|
||||
|
||||
Persistent ledger, exclusive flock, atomic durable reservation before run,
|
||||
each PNG, each click, camera and bounded pointer movement. Failed reservations
|
||||
remain consumed; no reset/refund or alternate LAB. Offline tests must precede run.
|
||||
Limits: run 1, pristine 0, SceneFX/Mosaic builds 0, camera adjustment 1, each of
|
||||
three named clicks 1, PNG 12, pointer moves 8, duration 150 seconds. Expected
|
||||
PNG count 8. All generated artifacts stay outside Git. Seal source and evidence
|
||||
manifests once after collecting results, then verify every entry without rewriting.
|
||||
|
||||
Keep client damage requests, scene/output damage extents and stabilized PNGs
|
||||
separate. No claim of complete intermediate frames or native output-buffer/PNG
|
||||
identity. Root/nested grabs, other geometry, migration, saved buffers/effects,
|
||||
full semantic ABI, other wlroots pairs, legal notice closure and named release/
|
||||
security owners are NOT TESTED or remain open. D1–D4 stay approved privately.
|
||||
|
||||
FEASIBLE requires every V1–V4 assertion and conformant binary/dependencies,
|
||||
no crash or blocking defect in this matrix. PARTIALLY_FEASIBLE retains demonstrated
|
||||
principal properties with unqualified input/return/pixels. INCONCLUSIVE means an
|
||||
unreliable fixture. BLOCKED requires demonstrated pixel/input leakage, runtime
|
||||
ABI defect or standard regression. Focus/calibration error alone is not a clipping
|
||||
failure. Architecture: STATIC_MOSAIC_FOCUS_INPUT_STANDARD_FEASIBLE_FOR_TESTED_MATRIX,
|
||||
STATIC_MOSAIC_FOCUS_INPUT_STANDARD_REQUIRES_REDESIGN, or UNDECIDED respectively.
|
||||
|
||||
## Independent future reconstruction (not authorized in this campaign)
|
||||
|
||||
A later campaign can archive pinned Mosaic cd550382b311efdbfe3ed892f4e1d281d852e754,
|
||||
SceneFX 37ccd723bef49e6891156ffafce8f549f01446cc and wlroots
|
||||
d783533489e1f75d6886c2ab5c5960090ef268f8, without the old LAB. Apply the ordered
|
||||
three SceneFX and five Mosaic patches listed in the
|
||||
[static distribution report](STEP20C_SCENEFX_STATIC_DISTRIBUTION_CONTRACT.md).
|
||||
Use SceneFX default_library=static/examples=false, its uninstalled pkg-config
|
||||
provider and Mosaic --prefix=/usr --bindir=bin with mosaic-package=true,
|
||||
swaybar/swaynag=false, man-pages=disabled, default-wallpaper=false,
|
||||
sd-bus-provider=libsystemd, werror=false. Build in independent directories,
|
||||
qualify exact shared wlroots, model, ELF, notices/stage and new authorized
|
||||
runtime budgets. Preserve toolchain/generated protocols/patch hashes. This is
|
||||
source reconstruction, not a promise of byte-reproducibility or permission to
|
||||
substitute another binary in this ticket.
|
||||
|
||||
The [fixture](../../tooling/probes/step20c-static-runtime-followup/README.md)
|
||||
provides the bounded runner, offline tests and evidence collector.
|
||||
|
||||
## Completed campaign — retained fixture failure
|
||||
|
||||
`SWAYFX_MOSAIC_STATIC_FOCUS_INPUT_STANDARD_PROBE=INCONCLUSIVE`
|
||||
|
||||
Architecture: **UNDECIDED**. LAB:
|
||||
`/home/fy59/swayfx-mosaic-static-followup-nhwejz6u`. One run, 2.661722 seconds.
|
||||
The unique runtime stopped before the first screenshot, movement or button.
|
||||
No restart, second LAB, binary/algorithm repair or budget refund occurred.
|
||||
|
||||
### Identity, setup and measured state
|
||||
|
||||
The copied executable retains SHA-256
|
||||
`f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b`,
|
||||
ELF build ID `942db64269640778ee433e5970ba1100bc60f94c`, mode 0755.
|
||||
Readelf and ldd -r pass; process maps resolve the qualified system wlroots
|
||||
0.20.2 binary with SHA-256
|
||||
`7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837`.
|
||||
No shared SceneFX or historical LAB dependency appears in the runtime mappings.
|
||||
Meson/model evidence is reused from A; no new compositor/library build.
|
||||
|
||||
Three C17 client-only builds from manifest-verified sources passed syntax and
|
||||
compilation and produced the same bytes as the historical executables:
|
||||
|
||||
| Client | SHA-256 |
|
||||
| --- | --- |
|
||||
| saved-client | `3d5d0c5e446b995c210006a5bafe0d86dd5f32a228e1b7137d12af6ec9e217da` |
|
||||
| static-reference | `11166ea1bb7cb3bb08f10ab93e091c5c5ea8687649b5fb0705288c7ca91680e8` |
|
||||
| pointer-daemon | `76ad0f093f2b7a97359920b7bd456cfa93a1581c73875547a86eb911f2ed2d7a` |
|
||||
|
||||
These hashes are newly recorded; the historical executable files themselves
|
||||
were not sealed. Historical C/generated protocol inputs were sealed and verified.
|
||||
The new retained source/command/binary chain supplies explicit provenance.
|
||||
|
||||
Both outputs matched the requested negative-origin 800×600/scale-1/normal
|
||||
matrix. Private configuration retained focus_follows_mouse no. The command
|
||||
`[con_id=7] border none` succeeded for reference PID 1674352 on workspace 2
|
||||
(ID 5), H2. Filler ID 8, with the same App ID, kept normal border and width 2.
|
||||
Target ID 9/PID 1674361 on workspace 1 (ID 6), H1 remained focused; reference
|
||||
was unfocused. No broad App ID rule affected the filler.
|
||||
|
||||
Applied camera read 0 before calibration. The target's horizontal content offset
|
||||
was -158 with configured width 636; desired left -318 gave signed delta +160.
|
||||
IPC and scene both read 160 afterward. Applied owner scene became (-320,0),
|
||||
size 640×600. IPC window offset x=2 and actual client COMMIT size 636×573
|
||||
support horizontal content continuation [-318,318), crossing x=0. They do not
|
||||
prove native buffer y or actually visible pixels without a capture.
|
||||
PI_SCENE records tile-local (-160,0), clip (160,0,800,600), translated through
|
||||
H1 origin (-800,0) to global clip (-800,0,800,600). H2 records clip_valid=0.
|
||||
This proves the observed applied clip state, not the unperformed pixel/input gates.
|
||||
|
||||
### Exact stopping assertion and source diagnosis
|
||||
|
||||
The executed runner required both border none and current_border_width==0.
|
||||
Reference IPC instead reports:
|
||||
|
||||
```json
|
||||
{"border":"none","current_border_width":2,
|
||||
"rect":{"x":0,"y":0,"width":800,"height":600},
|
||||
"deco_rect":{"x":0,"y":0,"width":0,"height":0},
|
||||
"window_rect":{"x":0,"y":0,"width":800,"height":600}}
|
||||
```
|
||||
|
||||
This failed the stored-width predicate before writing owner-geometry.json or
|
||||
reference-preflight.json and before any capture reservation. Offline collection
|
||||
reconstructs the actual owner/reference observations from retained IPC/traces
|
||||
and explicitly labels all unexecuted results NOT TESTED.
|
||||
|
||||
Source diagnosis: `sway/commands/border.c:cmd_border` calls set_border(B_NONE);
|
||||
it changes border_thickness only with a second argument. `sway/ipc-json.c`
|
||||
publishes current.border_thickness unconditionally. `sway/desktop/transaction.c`
|
||||
sets the effective border width/top to zero for B_NONE. Therefore stored width 2
|
||||
is compatible with no border. The runner confused stored configuration with
|
||||
effective decoration geometry. No rendered border or Mosaic leak is demonstrated.
|
||||
The initial protocol's zero-width expectation above is retained as the failed
|
||||
hypothesis; it is not a new product contract. The exact executed runner remains
|
||||
unchanged, and no corrected runner is represented as executed.
|
||||
|
||||
### Matrix, damage and cleanup
|
||||
|
||||
| Gate | Result |
|
||||
| --- | --- |
|
||||
| Binary, loader, exact wlroots | PASS |
|
||||
| Output geometry, client mapping, focus, targeted border mode | PASS IPC/source evidence |
|
||||
| Calculated camera and applied clip | OBSERVED; no pixel qualification |
|
||||
| Two H2 reference captures / whole RGB equality | NOT TESTED; zero PNG |
|
||||
| H1 retained pixels / H2 forbidden colors | NOT TESTED in B |
|
||||
| Visible Mosaic button | NOT TESTED |
|
||||
| Invisible Mosaic non-delivery / reference H2 delivery | NOT TESTED |
|
||||
| H2 focus-transition RGB stability | NOT TESTED |
|
||||
| splith return / clip removal / standard button | NOT TESTED |
|
||||
| Root/nested popup or grab | NOT TESTED, deliberately outside scope |
|
||||
| Damage | Setup/client/scene/output extents retained; no PNG correlation or intermediate-frame coverage |
|
||||
| Crash/protocol error | No crash; each ordinary client exited 0 with error=0 |
|
||||
| Cleanup | PASS; compositor exit 0, three clients exit 0, owned pointer terminated -15; PIDs and private runtime absent |
|
||||
|
||||
The owned runtime was `/tmp/msr-330t08c8`, removed after all owned process groups
|
||||
were reaped. No daily IPC, installation, system dependency replacement, package,
|
||||
production patch/source, UX, dotfile, QuickShell, Codex or STEP20D change occurred.
|
||||
|
||||
Budget: SceneFX build 0, Mosaic build 0, binary modification 0, run 1/1, pristine
|
||||
0, camera 1/1, captures 0/12, each named click 0/1, moves 0/8. The consumed run
|
||||
prevents further execution despite unused PNG/click capacity. The persistent
|
||||
owner registration and ledger are retained and never reset.
|
||||
|
||||
The future smallest justified gate is a separately authorized single run after
|
||||
correcting the fixture's border-semantic predicate and testing it offline against
|
||||
this retained border-none/width-2 snapshot. Keep the same focus/RGB/input/splith
|
||||
objectives and exact candidate identity if still available. No SceneFX or clip
|
||||
algorithm modification is justified by this fixture failure. This ticket does
|
||||
not close the static runtime continuity gate or authorize STEP20C integration.
|
||||
|
||||
Q1–Q2 reference stability and full RGB: NOT TESTED. Q3 forbidden pixels: NOT
|
||||
TESTED in B; A's zero target colors remain historical only. Q4–Q9 button delivery,
|
||||
invisible input, focus RGB, clip removal and standard input: NOT TESTED. Q10
|
||||
static/shared behavioral equivalence: not established by this run. Q11 algorithm
|
||||
change justified: no demonstrated runtime anomaly. Q12 wider geometry, lifecycle,
|
||||
full damage/effects/saved buffers, applied-generation and exhaustive ABI remain
|
||||
unqualified, as do this run's intended principal gates. Q13 preparation for
|
||||
integration: the static build/load candidate is established, but the requested
|
||||
continuity evidence remains open. Q14 next technical gate: correct the fixture
|
||||
and obtain a newly authorized bounded RGB/input/standard-return observation.
|
||||
D1–D4 persist; STEP20C NEXT, production multi-output NOT YET SUPPORTED.
|
||||
|
||||
## Validation and integrity receipt
|
||||
|
||||
Python AST, shell/recipe syntax, concurrent/exhausted persistent-ledger tests,
|
||||
exact-RGB and actual-button offline tests, retained border snapshot/source
|
||||
semantics, local Markdown links, source-only fixture, protected source/system
|
||||
hashes, unchanged executed runner and owned-process/socket cleanup: PASS.
|
||||
Runtime scenario: FAIL at fixture assertion, scientific verdict INCONCLUSIVE.
|
||||
Meson/model: historical qualified results reused, NOT RUN anew. Standard test
|
||||
suite/headless campaign: NOT RUN, would exceed the authorized runtime scope.
|
||||
No subagent review was requested; the parent performed the bounded source,
|
||||
contract, provenance and final evidence audit. No independent review is claimed.
|
||||
|
||||
188 TEMPORAL_DAMAGE trace lines are retained from setup. They establish no
|
||||
post-click, standard-return or every-frame damage result.
|
||||
|
||||
B source manifest: 24 entries, SHA-256
|
||||
`5f9ab2b6d1ca23ef7743623d1e544237c8b70b62fe6de8164a46f1342e5a8ee2`.
|
||||
B evidence manifest: 46 entries, SHA-256
|
||||
`41cc5e3d305d89e83314723e67d7116c36d1503dd35647894bc898c49343e1a5`.
|
||||
Both were created once and every entry verified. Executed fixture and later
|
||||
collector/final fixture are distinct source directories. The report and final
|
||||
Git diff are outside these inventories to avoid circular digests. A's original
|
||||
manifests still verify without rewriting or snapshot remapping.
|
||||
|
||||
Final tracked B delta: this report plus Architecture, Known Limitations,
|
||||
Roadmap, documentation index and Changelog; source-only follow-up fixture added.
|
||||
No AGENTS, UX, Packaging, PKGBUILD, cumulative patch or production source change
|
||||
in B. Local main and both remotes remain the A publication commit
|
||||
`5e2961d05b7b33252718aabc4dbf787724f79ef0`; B remains unstaged and uncommitted.
|
||||
|
||||
## Static focus fixture evidence freeze — 2026-10-13
|
||||
|
||||
This new freeze task starts at main
|
||||
`5e2961d05b7b33252718aabc4dbf787724f79ef0`. Direct `git ls-remote`
|
||||
queries to canonical Forgejo and GitHub both returned that exact commit.
|
||||
The expected local documentation and source-only fixture were audited in full.
|
||||
No foreign change was found. This preservation commit does not alter the
|
||||
historical executed runner, either historical manifest, or any raw observation.
|
||||
The earlier LOCAL/UNCOMMITTED statements describe the original campaign's state,
|
||||
not the publication state of this frozen evidence.
|
||||
|
||||
All 24 source and 46 evidence entries pass their original SHA-256 inventories;
|
||||
the manifest digests above are unchanged. The Python files match the sealed
|
||||
final-fixture byte for byte, and run.py matches the actually executed fixture.
|
||||
Python AST, offline RGB/button tests, concurrent/exhausted ledger tests,
|
||||
read-only collector verification, shell recipe syntax, local Markdown links
|
||||
and git diff --check pass. Production source, package recipe and cumulative
|
||||
patch are unchanged. No graphical test, compositor/library build or historical
|
||||
budget reservation is performed by this freeze. Run 1/1 and camera 1/1 remain
|
||||
consumed; unused captures/clicks/moves cannot authorize another historical run.
|
||||
|
||||
The source audit also reads the pristine pinned SwayFX border command:
|
||||
`fd71a6bdc061bd633b488ae7b83e8a6981d22f86:sway/commands/border.c`.
|
||||
Its B_NONE handling and optional second-argument thickness assignment match
|
||||
the retained candidate semantics. IPC thickness is stored metadata; effective
|
||||
border/decor geometry is separate. The failure remains INCONCLUSIVE / UNDECIDED.
|
||||
The separately authorized RGB/input/splith ticket must supply a corrected pure
|
||||
predicate, replay actual IPC and audit the entire preflight before its own run.
|
||||
|
||||
`SWAYFX_MOSAIC_STATIC_FOCUS_FIXTURE_FREEZE=PASS`
|
||||
76
tooling/probes/step20c-static-runtime-followup/README.md
Normal file
76
tooling/probes/step20c-static-runtime-followup/README.md
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# Static focus/input/standard-return fixture
|
||||
|
||||
EXPERIMENTAL / FROZEN HISTORICAL FAILURE. No production integration.
|
||||
Read the [protocol](../../../docs/design/STEP20C_STATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md).
|
||||
The previous static campaign remains sealed and is never replayed.
|
||||
|
||||
This fixture creates one campaign-owned LAB outside Git, copies the hash-qualified
|
||||
static compositor unchanged and rebuilds only three historical C17 clients from
|
||||
manifest-verified source/generated protocols. The old client executables were
|
||||
not manifest entries, so their observed hashes are contextual only. An exclusive
|
||||
`~/swayfx-mosaic-static-followup-v1.owner` file binds the campaign to one LAB.
|
||||
Never delete/reset it, its ledger, or replace the LAB to retry.
|
||||
|
||||
Offline checks before launch:
|
||||
|
||||
```sh
|
||||
PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-runtime-followup/test-budget.py
|
||||
PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-runtime-followup/test-offline.py
|
||||
```
|
||||
|
||||
Historical commands, preserved for provenance only — DO NOT execute prepare.py
|
||||
or run.py again; the historical budget is exhausted:
|
||||
|
||||
```sh
|
||||
PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-runtime-followup/prepare.py
|
||||
# Use the printed LAB path; execute the retained exact fixture copy there.
|
||||
PYTHONDONTWRITEBYTECODE=1 python "$LAB/fixture/run.py" "$LAB"
|
||||
PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-runtime-followup/collect.py "$LAB"
|
||||
```
|
||||
|
||||
The flock ledger reserves run, PNG, named click, camera and pointer moves before
|
||||
execution. No refund/reset. Runtime is private mode 0700; all swaymsg use explicit
|
||||
-s, child environments discard inherited desktop sockets. Only owned process
|
||||
groups are stopped. The deadline is 150 seconds, per-log bound 64 MiB. Sources,
|
||||
raw protocol/client/scene logs, PNGs, IPC snapshots, timestamps, serials, exit
|
||||
codes and result manifests stay external. The collector creates manifests once;
|
||||
verification is read-only with `collect.py "$LAB" --verify`.
|
||||
|
||||
Reference border none is applied to its measured con_id, never to the shared
|
||||
App ID (which also identifies the H1 filler). H2 full RGB must be identical
|
||||
before input; no crop/tolerance. Clicks use measured painted interiors. The
|
||||
standard-return command explicitly targets the H1 con_id after H2 gains focus.
|
||||
Popup tests and native output-buffer/PNG identity remain NOT TESTED.
|
||||
|
||||
The protocol describes independent source reconstruction for a future authorized
|
||||
campaign if historical binaries disappear. Do not silently rebuild or substitute
|
||||
the compositor in this campaign. Historical LAB paths identify retained evidence,
|
||||
not a permanent production dependency.
|
||||
|
||||
## Actual execution — no retry permitted
|
||||
|
||||
LAB `/home/fy59/swayfx-mosaic-static-followup-nhwejz6u`, run consumed.
|
||||
Verdict INCONCLUSIVE, architecture UNDECIDED. The runner stopped before the
|
||||
first PNG because it incorrectly required current_border_width=0 after border
|
||||
none. Actual IPC had border none, stored width 2, empty deco_rect and 800×600
|
||||
window_rect. The filler retained normal decorations; target/workspace 1 were
|
||||
focused. The source confirms that border none changes the border enum, not
|
||||
stored thickness. This is a fixture defect, not an observed rendered border.
|
||||
|
||||
The executed runner is intentionally unchanged, including the erroneous predicate.
|
||||
It is historical experimental source, not a validated replay harness. A future
|
||||
authorized campaign must correct that semantic check and add its regression
|
||||
before a separately budgeted run; this campaign may not be resumed or repeated.
|
||||
No PNG, click or splith command was executed. Camera 0→160 and applied clip were
|
||||
observed. All principal RGB/input/standard gates remain NOT TESTED. The collector
|
||||
records those omissions explicitly and verifies owned-process/socket cleanup.
|
||||
|
||||
## Freeze receipt
|
||||
|
||||
The preservation task verified every entry of the original 24-source and
|
||||
46-evidence manifests without rewriting them. All Python files match the
|
||||
sealed final-fixture; run.py matches the historical executed fixture, including
|
||||
the invalid width assertion. Offline tests and --verify remain read-only
|
||||
checks of this exhausted campaign. Publication preserves INCONCLUSIVE /
|
||||
UNDECIDED, zero PNG and zero clicks; it does not convert the fixture into a
|
||||
validated runner. A new campaign uses a separate directory and budget.
|
||||
62
tooling/probes/step20c-static-runtime-followup/collect.py
Normal file
62
tooling/probes/step20c-static-runtime-followup/collect.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Offline collection of the single stopped run; never creates runtime evidence.
|
||||
|
||||
WHY: an IPC metadata predicate is not a pixel or input failure.
|
||||
CONTRACT: retain the executed runner unchanged and label unexercised cells.
|
||||
INVARIANT: seal once; --verify never rewrites results or manifests.
|
||||
"""
|
||||
import argparse, json, re, shutil
|
||||
from pathlib import Path
|
||||
from support import save, sha
|
||||
p=argparse.ArgumentParser();p.add_argument('lab',type=Path);p.add_argument('--verify',action='store_true');a=p.parse_args();lab=a.lab.resolve()
|
||||
assert (Path.home()/'swayfx-mosaic-static-followup-v1.owner').read_text().strip()==str(lab)
|
||||
results=lab/'results'
|
||||
if not a.verify:
|
||||
assert not (results/'evidence-sha256.txt').exists()
|
||||
runtime=json.loads((results/'runtime.json').read_text())
|
||||
snap=json.loads((results/'preflight-snapshot.json').read_text())
|
||||
def nodes(n):
|
||||
yield n
|
||||
for c in n.get('nodes',[])+n.get('floating_nodes',[]):yield from nodes(c)
|
||||
ns=list(nodes(snap['tree']))
|
||||
ref=next(n for n in ns if n.get('app_id')=='org.mosaic.StaticReference' and n['rect']['x']==0)
|
||||
target=next(n for n in ns if n.get('app_id')=='org.mosaic.SavedBufferProbe')
|
||||
filler=next(n for n in ns if n.get('app_id')=='org.mosaic.StaticReference' and n['id']!=ref['id'])
|
||||
assert ref['border']=='none' and ref['current_border_width']==2
|
||||
assert ref['deco_rect']==dict(x=0,y=0,width=0,height=0)
|
||||
assert ref['window_rect']==dict(x=0,y=0,width=800,height=600)
|
||||
assert filler['border']=='normal' and target['focused'] and not ref['focused']
|
||||
assert runtime['status']=='INCONCLUSIVE' and runtime['failure']=='AssertionError: '
|
||||
budget=json.loads((results/'budget.json').read_text())
|
||||
assert budget['used']==dict(run=1,capture=0,camera=1,visible_click=0,forbidden_click=0,standard_click=0,move=0)
|
||||
assert not list((lab/'run').glob('*.png')) and runtime['duration_seconds']<150
|
||||
for name in ['target','reference','filler']:
|
||||
text=(lab/'run'/(name+'.log')).read_text()
|
||||
assert not any(s.startswith('BUTTON ') for s in text.splitlines())
|
||||
assert 'error=0' in text
|
||||
for rec in runtime['process_exit_codes']:assert not Path('/proc',str(rec['pid'])).exists()
|
||||
assert not Path(runtime['runtime_removed']).exists()
|
||||
compositor=(lab/'run/compositor.log').read_text()
|
||||
owner=[s for s in compositor.splitlines() if 'PI_OWNER output=HEADLESS-1 ' in s and f' id={target["id"]} ' in s][-1]
|
||||
clips=[s for s in compositor.splitlines() if 'PI_SCENE output=' in s]
|
||||
save(lab,'fixture-incident',{'status':'CONFIRMED FIXTURE ERROR','failed_predicate':'reference current_border_width == 0','actual':ref,'filler':filler,'reason':'border none sets enum B_NONE without resetting stored thickness; IPC reports stored thickness even when rendered border is disabled','source_sites':['sway/commands/border.c:cmd_border','sway/ipc-json.c:ipc_json_describe_container','sway/desktop/transaction.c:arrange_container'],'runner_modified_after_run':False,'rerun':False})
|
||||
save(lab,'owner-geometry',{'status':'OBSERVED scene and IPC only','target':target,'scene_trace':owner,'client_commits':[s for s in (lab/'run/target.log').read_text().splitlines() if s.startswith('COMMIT ')],'painted_content':'NOT TESTED; no PNG','native_buffer_rectangle':'NOT ESTABLISHED'})
|
||||
save(lab,'clip-observations',{'status':'OBSERVED applied traces only','traces':clips,'pixel_validation':'NOT TESTED'})
|
||||
for name in ['reference-preflight','reference-pixel-analysis','permitted-pixels','forbidden-pixels','target-input','reference-input','standard-return']:
|
||||
save(lab,name,{'status':'NOT TESTED','reason':'run stopped at incorrect stored-border-width assertion before first capture or injection; no retry'})
|
||||
matrix={'binary_identity':'PASS','loaded_libraries':'PASS','output_geometry':'PASS','reference_border_mode':'PASS IPC none; stored thickness 2','reference_visible_pixels':'NOT TESTED','initial_focus':'PASS target/workspace 1','camera':'PASS calculated +160, applied 0 to 160','applied_clip':'OBSERVED global [-800,0,800,600]','H2_preflight':'NOT TESTED','H1_pixels':'NOT TESTED','H2_pixels':'NOT TESTED','whole_H2_RGB':'NOT TESTED','visible_click':'NOT TESTED','forbidden_click':'NOT TESTED','reference_delivery':'NOT TESTED','focus_RGB_stability':'NOT TESTED','splith_return':'NOT TESTED','clip_removal':'NOT TESTED','standard_click':'NOT TESTED','popups':'NOT TESTED','damage':'available setup extents only; no PNG or every-frame proof','cleanup':'PASS'}
|
||||
save(lab,'matrix',matrix)
|
||||
save(lab,'summary',{'verdict':'INCONCLUSIVE','architecture':'UNDECIDED','reason':'incorrect fixture interpretation of current_border_width under border none stopped the only run before RGB/input gates','budget':budget['used'],'builds':{'SceneFX':0,'Mosaic':0,'historical_clients_only':3},'STEP20C':'NEXT','production_multi_output':'NOT YET SUPPORTED','reruns':0,'new_labs':1})
|
||||
# Keep the executed fixture separate from the later offline collector.
|
||||
shutil.copytree(Path(__file__).parent,lab/'final-fixture')
|
||||
source=sorted(p for root in ['fixture','final-fixture','client-source'] for p in (lab/root).rglob('*') if p.is_file())
|
||||
evidence=sorted(p for root in ['raw','run','results'] for p in (lab/root).rglob('*') if p.is_file())
|
||||
evidence += [lab/name for name in ['swayfx-mosaic','saved-client','static-reference','pointer-daemon','campaign-owner']]
|
||||
for name,files in [('source',source),('evidence',evidence)]:
|
||||
with (results/(name+'-sha256.txt')).open('x') as out:
|
||||
for f in files:out.write(f'{sha(f)} {f}\n')
|
||||
for name in ['source','evidence']:
|
||||
manifest=results/(name+'-sha256.txt');lines=manifest.read_text().splitlines()
|
||||
for line in lines:
|
||||
digest,path=line.split(' ',1);assert sha(path)==digest,path
|
||||
print(name,len(lines),sha(manifest),'PASS')
|
||||
31
tooling/probes/step20c-static-runtime-followup/pixels.py
Normal file
31
tooling/probes/step20c-static-runtime-followup/pixels.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
"""Exact whole-image comparisons and measured interior click selection.
|
||||
|
||||
CONTRACT: no mask or tolerance may turn a whole-output difference into PASS.
|
||||
INVARIANT: coordinates of every differing pixel are retained on failure.
|
||||
"""
|
||||
from PIL import Image, ImageChops
|
||||
import re
|
||||
COLORS = {(35,68,204),(32,208,160)}
|
||||
def analyze(im):
|
||||
mask=Image.new('L',im.size)
|
||||
values=[255 if c in COLORS else 0 for c in im.get_flattened_data()]
|
||||
mask.putdata(values)
|
||||
return {'count':sum(v!=0 for v in values),'bbox':mask.getbbox()}
|
||||
def compare(a,b):
|
||||
assert a.size==b.size
|
||||
points=[{'x':i%a.width,'y':i//a.width,'before':x,'after':y}
|
||||
for i,(x,y) in enumerate(zip(a.get_flattened_data(),b.get_flattened_data())) if x!=y]
|
||||
return {'equal':not points,'changed':len(points),'bbox':ImageChops.difference(a,b).getbbox(),'pixels':points}
|
||||
def interior(im):
|
||||
box=analyze(im)['bbox'];assert box
|
||||
x1,y1,x2,y2=box;assert x2-x1>40 and y2-y1>40
|
||||
x,y=(x1+x2)//2,(y1+y2)//2
|
||||
assert all(im.getpixel((xx,yy)) in COLORS for xx in range(x-20,x+21) for yy in range(y-20,y+21))
|
||||
return x,y
|
||||
|
||||
def button_pair(lines):
|
||||
events=[re.search(r'BUTTON ns=(\d+) serial=(\d+) button=(\d+) state=(\d+)',x) for x in lines]
|
||||
events=[tuple(map(int,x.groups())) for x in events if x]
|
||||
assert len(events)==2 and [x[2:] for x in events]==[(273,1),(273,0)],events
|
||||
assert all(x[1]>0 for x in events)
|
||||
return [{'ns':x[0],'serial':x[1],'button':x[2],'state':x[3]} for x in events]
|
||||
46
tooling/probes/step20c-static-runtime-followup/prepare.py
Normal file
46
tooling/probes/step20c-static-runtime-followup/prepare.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Copy exact candidate and compile verified historical C17 clients only.
|
||||
|
||||
CONTRACT: exclusive campaign registration prevents another LAB/reset/retry.
|
||||
WHY: historical client executable hashes were not sealed; source provenance was.
|
||||
"""
|
||||
import json, os, shutil, stat, subprocess, tempfile
|
||||
from pathlib import Path
|
||||
from support import sha, command, save
|
||||
OLD=Path('/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt')
|
||||
OWNER=Path.home()/'swayfx-mosaic-static-followup-v1.owner'
|
||||
# Reserve campaign ownership before creating the only LAB, even on failure.
|
||||
fd=os.open(OWNER,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600)
|
||||
with os.fdopen(fd,'w') as f:
|
||||
lab=Path(tempfile.mkdtemp(prefix='swayfx-mosaic-static-followup-',dir=Path.home()))
|
||||
f.write(str(lab)+'\n');f.flush();os.fsync(f.fileno())
|
||||
for name in ['results','raw']:(lab/name).mkdir()
|
||||
(lab/'campaign-owner').write_text(str(OWNER)+'\n')
|
||||
for name,digest in [('source','ed55bd418595ca977c022fa98917a899072cf3b6f0fb15457db823081046e797'),('evidence','e260ce5793ffc9a29ee4bfa2189663feeb6dc025bd46d3f5ddebccd225f3eef1')]:
|
||||
manifest=OLD/'results'/(name+'-sha256.txt');assert sha(manifest)==digest
|
||||
for line in manifest.read_text().splitlines():
|
||||
h,p=line.split(' ',1);assert sha(p)==h,p
|
||||
binary=OLD/'stage-static/usr/bin/swayfx-mosaic'
|
||||
expected='f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b'
|
||||
assert sha(binary)==expected and stat.S_IMODE(binary.stat().st_mode)==0o755
|
||||
shutil.copy2(binary,lab/'swayfx-mosaic');assert sha(lab/'swayfx-mosaic')==expected
|
||||
elf=command(lab,'binary-elf',['readelf','-hW','-nW','-dW',lab/'swayfx-mosaic'])
|
||||
assert 'libscenefx' not in elf and '942db64269640778ee433e5970ba1100bc60f94c' in elf
|
||||
assert '(RPATH)' not in elf and '(RUNPATH)' not in elf
|
||||
loader=command(lab,'dependencies',['ldd','-r',lab/'swayfx-mosaic'])
|
||||
assert 'not found' not in loader and 'undefined symbol' not in loader and 'libscenefx' not in loader
|
||||
wl=Path('/usr/lib/libwlroots-0.20.so').resolve()
|
||||
assert sha(wl)=='7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837'
|
||||
assert str(wl) in loader
|
||||
save(lab,'binary-identity',{'status':'PASS','source':str(binary),'copy':str(lab/'swayfx-mosaic'),'sha256':expected,'build_id':'942db64269640778ee433e5970ba1100bc60f94c','mode':'0755','wlroots':str(wl),'wlroots_sha256':sha(wl),'new_compositor_builds':0})
|
||||
shutil.copytree(OLD/'client-source',lab/'client-source')
|
||||
flags=subprocess.check_output(['pkg-config','--cflags','--libs','wayland-client'],text=True).split()
|
||||
clients=[]
|
||||
for name,proto in [('saved-client','xdg-shell'),('static-reference','xdg-shell'),('pointer-daemon','virtual-pointer')]:
|
||||
src=lab/'client-source';argv=['cc','-std=c17','-Wall','-Wextra','-Werror','-Wno-unused-parameter','-I'+str(src),src/(name+'.c'),src/(proto+'-protocol.c'),*flags]
|
||||
command(lab,name+'-syntax',[*argv,'-fsyntax-only'])
|
||||
command(lab,name+'-compile',[*argv,'-o',lab/name])
|
||||
clients.append({'name':name,'source_sha256':sha(src/(name+'.c')),'binary_sha256':sha(lab/name),'historical_binary_sha256_unsealed':sha(OLD/name)})
|
||||
save(lab,'client-provenance',{'historical':json.loads((OLD/'results/client-provenance.json').read_text()),'source_manifest_verified':True,'clients':clients})
|
||||
shutil.copytree(Path(__file__).parent,lab/'fixture')
|
||||
print('LAB='+str(lab))
|
||||
249
tooling/probes/step20c-static-runtime-followup/run.py
Normal file
249
tooling/probes/step20c-static-runtime-followup/run.py
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
#!/usr/bin/env python3
|
||||
"""One budgeted follow-up of the unchanged verified static compositor.
|
||||
|
||||
WHY: resolved relocations do not demonstrate real compositor execution.
|
||||
CONTRACT: verify /proc executable/library identity before mapping any client.
|
||||
INVARIANT: owned sockets/groups only, one camera correction, durable captures.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
from PIL import Image,ImageChops
|
||||
from support import environment,reserve,save,sha
|
||||
from pixels import analyze, compare, interior, button_pair
|
||||
|
||||
p=argparse.ArgumentParser();p.add_argument('lab',type=Path);a=p.parse_args();lab=a.lab.resolve()
|
||||
assert json.loads((lab/'results/binary-identity.json').read_text())['status']=='PASS'
|
||||
assert sha(lab/'swayfx-mosaic')=='f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b'
|
||||
assert (Path.home()/'swayfx-mosaic-static-followup-v1.owner').read_text().strip()==str(lab)
|
||||
reserve(lab,'run');run=lab/'run';run.mkdir();runtime=Path(tempfile.mkdtemp(prefix='msr-',dir='/tmp'))
|
||||
runtime.chmod(0o700);(run/'runtime-path').write_text(str(runtime)+'\n')
|
||||
(run/'config').write_text('output HEADLESS-1 resolution 800x600 scale 1 transform normal position -800 0\noutput HEADLESS-2 resolution 800x600 scale 1 transform normal position 0 0\nworkspace 1 output HEADLESS-1\nworkspace 2 output HEADLESS-2\nfocus_follows_mouse no\nseat seat0 hide_cursor 100\n')
|
||||
env=environment();env.update(XDG_RUNTIME_DIR=str(runtime),WLR_BACKENDS='headless',WLR_HEADLESS_OUTPUTS='2',WLR_RENDERER='pixman')
|
||||
binary=lab/'swayfx-mosaic';processes=[];files=[];failure=None;compositor=None;events=[]
|
||||
started=time.monotonic();finished=threading.Event();expired=threading.Event()
|
||||
|
||||
def event(kind,**data):
|
||||
row={'ns':time.monotonic_ns(),'kind':kind,**data};events.append(row)
|
||||
with (run/'events.jsonl').open('a') as out:out.write(json.dumps(row)+'\n')
|
||||
|
||||
def stop_owned():
|
||||
for proc in list(processes):
|
||||
try:os.killpg(proc.pid,signal.SIGTERM)
|
||||
except ProcessLookupError:pass
|
||||
|
||||
def watchdog():
|
||||
while not finished.wait(.1):
|
||||
if time.monotonic()-started>150 or any(x.stat().st_size>64*1024*1024 for x in run.glob('*.log')):
|
||||
expired.set();stop_owned();return
|
||||
|
||||
thread=threading.Thread(target=watchdog,daemon=True);thread.start()
|
||||
|
||||
def spawn(name,argv,stdin=False,extra=None):
|
||||
assert not expired.is_set()
|
||||
out=(run/(name+'.log')).open('w');files.append(out)
|
||||
proc=subprocess.Popen(list(map(str,argv)),env={**env,**(extra or {})},stdin=subprocess.PIPE if stdin else subprocess.DEVNULL,stdout=out,stderr=subprocess.STDOUT,text=True,start_new_session=True)
|
||||
processes.append(proc);event('spawn',name=name,pid=proc.pid,argv=list(map(str,argv)));return proc
|
||||
|
||||
def wait(predicate,seconds=8):
|
||||
end=time.monotonic()+seconds
|
||||
while time.monotonic()<end:
|
||||
if expired.is_set():raise RuntimeError('deadline/log bound exceeded')
|
||||
if compositor and compositor.poll() is not None:raise RuntimeError('compositor exited '+str(compositor.returncode))
|
||||
value=predicate()
|
||||
if value:return value
|
||||
time.sleep(.025)
|
||||
raise RuntimeError('fixture wait timed out')
|
||||
|
||||
def log(name):return (run/(name+'.log')).read_text(errors='replace')
|
||||
|
||||
def ipc(*args):
|
||||
assert sock.is_relative_to(runtime) and sock.is_socket()
|
||||
result=subprocess.run(['swaymsg','-s',str(sock),*args],env=env,capture_output=True,text=True,timeout=5,check=True)
|
||||
data=json.loads(result.stdout);event('ipc',args=args,result=data);return data
|
||||
|
||||
def cmd(value):
|
||||
data=ipc(value);assert all(x.get('success') for x in data),(value,data)
|
||||
|
||||
def nodes(node):
|
||||
yield node
|
||||
for child in node.get('nodes',[])+node.get('floating_nodes',[]):yield from nodes(child)
|
||||
|
||||
def find(app):return next((n for n in nodes(ipc('-t','get_tree')) if n.get('app_id')==app),None)
|
||||
|
||||
def inject(action,x,y,kind='move'):
|
||||
reserve(lab,kind)
|
||||
assert -800<=x<800 and 0<=y<600
|
||||
marker=f'INJECT_DONE {action} {x+800},{y}';before=log('pointer').count(marker)
|
||||
pointer.stdin.write(f'{action} {x+800} {y}\n');pointer.stdin.flush()
|
||||
wait(lambda:log('pointer').count(marker)>before)
|
||||
time.sleep(.15);event('pointer',action=action,global_point=[x,y],target_tail=log('target').splitlines()[-6:] if (run/'target.log').exists() else [])
|
||||
|
||||
def capture(tag,output):
|
||||
reserve(lab,'capture');path=run/(tag+'.png')
|
||||
subprocess.run(['grim','-s','1','-o',output,str(path)],env=env,check=True,timeout=5,stdout=subprocess.DEVNULL)
|
||||
im=Image.open(path).convert('RGB');assert im.size==(800,600)
|
||||
event('capture',file=path.name,output=output,sha256=sha(path));return im
|
||||
|
||||
def measure(owner):
|
||||
rows=[x for x in log('compositor').splitlines() if 'PI_OWNER output=HEADLESS-1 ' in x and f' id={owner["id"]} ' in x]
|
||||
assert rows,'applied owner trace unavailable'
|
||||
line=rows[-1];m=re.search(r'scene_valid=1 scene=(-?\d+),(-?\d+) applied_size=(\d+),(\d+)',line);assert m,line
|
||||
x,y,w,h=map(int,m.groups());win=owner['window_rect']
|
||||
return {'id':owner['id'],'scene':[x,y,w,h],'window_rect':win,'horizontal_content':[x+win['x'],win['width']],'raw':line,'IPC':owner}
|
||||
|
||||
# WHY: global focus becomes H2 after the forbidden-region click. Workspace and
|
||||
# owner IDs, rather than global command context, bind the standard-return test.
|
||||
focus_states=[]
|
||||
def snapshot(tag):
|
||||
tree=ipc('-t','get_tree');outputs=ipc('-t','get_outputs');workspaces=ipc('-t','get_workspaces')
|
||||
row={'tag':tag,'ns':time.monotonic_ns(),'tree':tree,'outputs':outputs,'workspaces':workspaces,
|
||||
'focused':[n['id'] for n in nodes(tree) if n.get('focused')]}
|
||||
focus_states.append(row);save(lab,'focus-states',focus_states)
|
||||
save(lab,tag+'-snapshot',row)
|
||||
return tree
|
||||
|
||||
def workspace(tree,name):
|
||||
return next(n for n in nodes(tree) if n['type']=='workspace' and n['name']==name)
|
||||
|
||||
def scene(output):
|
||||
rows=[s for s in log('compositor').splitlines() if 'PI_SCENE output='+output+' ' in s]
|
||||
assert rows,'scene trace absent';line=rows[-1]
|
||||
m=re.search(r'workspace=(\d+) layout=(\d+) scroll=(-?[\d.]+) tile_scene=(-?\d+),(-?\d+) clip_valid=(\d+) clip=(-?\d+),(-?\d+),(\d+),(\d+)',line);assert m,line
|
||||
v=list(map(float,m.groups()))
|
||||
return {'workspace':int(v[0]),'layout_enum':int(v[1]),'scroll':v[2],
|
||||
'tile_local':v[3:5],'clip_valid':bool(v[5]),'clip_local':v[6:10],
|
||||
'global_clip':[(-800 if output=='HEADLESS-1' else 0)+v[3]+v[6],v[4]+v[7],v[8],v[9]],'raw':line}
|
||||
|
||||
def camera(tree):return workspace(tree,'1')['mosaic']['rows'][0]['scroll_x']
|
||||
def buttons(name):return [x for x in log(name).splitlines() if x.startswith('BUTTON ')]
|
||||
def measured_click(kind,point):
|
||||
before={n:len(buttons(n)) for n in ['target','reference','filler']}
|
||||
inject('right',*point,kind=kind)
|
||||
row={'global_point':point,'events':{n:buttons(n)[before[n]:] for n in before},
|
||||
'tails':{n:log(n).splitlines()[-15:] for n in before},'ns':time.monotonic_ns()}
|
||||
save(lab,kind,row);return row
|
||||
|
||||
def whole(tag,baseline,im):
|
||||
value=compare(baseline,im);save(lab,tag,value);assert value['equal'],'whole H2 differs: '+tag
|
||||
return value
|
||||
|
||||
try:
|
||||
compositor=spawn('compositor',[binary,'-d','-c',run/'config'])
|
||||
sock=wait(lambda:next(iter(runtime.glob('sway-ipc.*.sock')),None))
|
||||
display=wait(lambda:next((x for x in runtime.glob('wayland-*') if x.is_socket()),None))
|
||||
env.update(SWAYSOCK=str(sock),WAYLAND_DISPLAY=display.name)
|
||||
maps=Path(f'/proc/{compositor.pid}/maps').read_text();(run/'proc-maps.txt').write_text(maps)
|
||||
exe=Path(f'/proc/{compositor.pid}/exe').resolve();assert exe==binary and sha(exe)==sha(binary)
|
||||
paths=sorted({line.split()[-1] for line in maps.splitlines() if len(line.split())>=6 and line.split()[-1].startswith('/')})
|
||||
wl=Path('/usr/lib/libwlroots-0.20.so').resolve()
|
||||
assert not any('libscenefx' in x for x in paths)
|
||||
assert [Path(x).resolve() for x in paths if 'libwlroots' in x]==[wl]
|
||||
assert sha(wl)=='7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837'
|
||||
assert not any('/swayfx-mosaic-' in x and Path(x)!=binary for x in paths)
|
||||
save(lab,'dependency-mappings',{'status':'PASS','exe':str(exe),'paths':paths,'wlroots_sha256':sha(wl),'SceneFX_shared':False})
|
||||
outputs=ipc('-t','get_outputs');actual={o['name']:(o['rect']['x'],o['rect']['y'],o['rect']['width'],o['rect']['height'],o['scale'],o['transform']) for o in outputs if o['active']}
|
||||
assert actual=={'HEADLESS-1':(-800,0,800,600,1.0,'normal'),'HEADLESS-2':(0,0,800,600,1.0,'normal')},actual
|
||||
save(lab,'output-geometry',outputs)
|
||||
pointer=spawn('pointer',[lab/'pointer-daemon','1600','600'],stdin=True);wait(lambda:'POINTER_READY' in log('pointer'))
|
||||
cmd('workspace 2');ref=spawn('reference',[lab/'static-reference'],stdin=True,extra={'WAYLAND_DEBUG':'1'})
|
||||
reference=wait(lambda:find('org.mosaic.StaticReference'));refid=reference['id']
|
||||
cmd(f'[con_id={refid}] border none')
|
||||
cmd('workspace 1');filler=spawn('filler',[lab/'static-reference'],stdin=True)
|
||||
wait(lambda:len([n for n in nodes(ipc('-t','get_tree')) if n.get('app_id')=='org.mosaic.StaticReference'])==2)
|
||||
target=spawn('target',[lab/'saved-client'],stdin=True,extra={'WAYLAND_DEBUG':'1'})
|
||||
wait(lambda:find('org.mosaic.SavedBufferProbe'))
|
||||
cmd('layout mosaic');time.sleep(.6)
|
||||
tree=snapshot('before-camera');owner=find('org.mosaic.SavedBufferProbe');targetid=owner['id'];wsid=workspace(tree,'1')['id']
|
||||
before=measure(owner);sc_before=scene('HEADLESS-1');scroll_before=camera(tree)
|
||||
assert sc_before['scroll']==scroll_before
|
||||
x,w=before['horizontal_content'];desired=-w//2;delta=x-desired
|
||||
reserve(lab,'camera');cmd(f'layout mosaic scroll {delta}')
|
||||
time.sleep(.6);owner=find('org.mosaic.SavedBufferProbe');after=measure(owner)
|
||||
tree=snapshot('preflight');scroll_after=camera(tree);sc_after=scene('HEADLESS-1')
|
||||
save(lab,'camera-calibration',{'before':scroll_before,'delta':delta,'desired_left':desired,'after':scroll_after,'before_scene':sc_before,'after_scene':sc_after})
|
||||
assert scroll_after==scroll_before+delta and sc_after['scroll']==scroll_after
|
||||
x,w=after['horizontal_content'];assert x<0<x+w and -800<x
|
||||
assert sc_after['clip_valid'] and sc_after['global_clip']==[-800,0,800,600]
|
||||
assert sc_after['workspace']==wsid
|
||||
reference=next(n for n in nodes(tree) if n['id']==refid)
|
||||
assert reference['pid']==ref.pid and reference['border']=='none' and reference['current_border_width']==0 and reference['visible']
|
||||
assert reference in list(nodes(workspace(tree,'2')))
|
||||
assert reference['rect']=={'x':0,'y':0,'width':800,'height':600}
|
||||
assert not reference['focused'] and owner['focused']
|
||||
assert next(z for z in ipc('-t','get_workspaces') if z['focused'])['name']=='1'
|
||||
save(lab,'owner-geometry',{'before':before,'after':after,'reference':reference,'last_client_commit':[s for s in log('target').splitlines() if s.startswith('COMMIT ')][-1]})
|
||||
save(lab,'clip-observations',{'mosaic':sc_after,'H2':scene('HEADLESS-2')})
|
||||
inject('move',-790,590);time.sleep(.6)
|
||||
baseline=capture('h2-reference-1','HEADLESS-2');time.sleep(.6);second=capture('h2-reference-2','HEADLESS-2')
|
||||
comparison=compare(baseline,second)
|
||||
save(lab,'reference-preflight',{'H2_REFERENCE_STABLE':comparison['equal'],'H2_WHOLE_RGB_EQUAL':comparison['equal'],'reference':reference,'focus':focus_states[-1]['focused'],'hashes':{n:sha(run/n) for n in ['h2-reference-1.png','h2-reference-2.png']}})
|
||||
save(lab,'reference-pixel-analysis',comparison)
|
||||
assert comparison['equal'] and all(c==(48,48,56) for c in baseline.get_flattened_data()),'reference preflight failed'
|
||||
left=capture('h1-mosaic','HEADLESS-1');right=capture('h2-mosaic','HEADLESS-2')
|
||||
visible=analyze(left);forbidden=analyze(right)
|
||||
save(lab,'permitted-pixels',visible);save(lab,'forbidden-pixels',forbidden)
|
||||
assert visible['count']>0 and forbidden['count']==0
|
||||
whole('v1-H2-comparison',baseline,right)
|
||||
bx,by,bw,bh=visible['bbox'];assert bx-800==x and bw==800
|
||||
last_commit=[s for s in log('target').splitlines() if s.startswith('COMMIT ')][-1]
|
||||
dimensions=re.search(r'size=(\d+)x(\d+)',last_commit);assert dimensions
|
||||
assert int(dimensions[1])==w and bh-by==int(dimensions[2])
|
||||
# Paint proves vertical position; only horizontal continuation is inferred
|
||||
# from the applied scene, IPC offset and actual configured client width.
|
||||
px,py=interior(left);permitted=(px-800,py)
|
||||
forbidden_point=(min(100,int(x+w)-30),py);assert forbidden_point[0]>=20
|
||||
assert x<forbidden_point[0]<x+w and right.getpixel(forbidden_point)==(48,48,56)
|
||||
save(lab,'click-points',{'permitted':permitted,'forbidden':forbidden_point,'paint_bbox':visible['bbox'],'horizontal_content':[x,w]})
|
||||
click=measured_click('visible_click',permitted);events_visible=button_pair(click['events']['target'])
|
||||
assert not click['events']['reference'] and not click['events']['filler']
|
||||
save(lab,'target-input',{'status':'PASS visible','point':permitted,'events':events_visible})
|
||||
tree=snapshot('after-visible');assert camera(tree)==scroll_after
|
||||
assert measure(find('org.mosaic.SavedBufferProbe'))['horizontal_content']==[x,w]
|
||||
click=measured_click('forbidden_click',forbidden_point);events_reference=button_pair(click['events']['reference'])
|
||||
assert not click['events']['target'] and not click['events']['filler'],'invisible target or wrong client input'
|
||||
save(lab,'reference-input',{'status':'PASS','point':forbidden_point,'events':events_reference,'invisible_target_events':click['events']['target']})
|
||||
tree=snapshot('after-reference');assert camera(tree)==scroll_after
|
||||
assert next(n for n in nodes(tree) if n['id']==refid)['focused']
|
||||
inject('move',-790,590);time.sleep(.6)
|
||||
whole('post-click-H2-comparison',baseline,capture('h2-after-clicks','HEADLESS-2'))
|
||||
cmd(f'[con_id={targetid}] layout splith');time.sleep(.6)
|
||||
tree=snapshot('standard');standard_ws=workspace(tree,'1');assert standard_ws['id']==wsid and standard_ws['layout']=='splith'
|
||||
standard_owner=next(n for n in nodes(standard_ws) if n['id']==targetid);assert standard_owner['visible']
|
||||
standard_left=capture('h1-standard','HEADLESS-1');standard_right=capture('h2-standard','HEADLESS-2')
|
||||
whole('standard-H2-comparison',baseline,standard_right)
|
||||
standard_scene=scene('HEADLESS-1');standard_h2=scene('HEADLESS-2')
|
||||
assert standard_scene['workspace']==wsid and not standard_scene['clip_valid'] and not standard_h2['clip_valid']
|
||||
sx,sy=interior(standard_left);point=(sx-800,sy)
|
||||
click=measured_click('standard_click',point);events_standard=button_pair(click['events']['target'])
|
||||
assert not click['events']['reference'] and not click['events']['filler']
|
||||
snapshot('after-standard-click');inject('move',-790,590);time.sleep(.6)
|
||||
whole('final-H2-comparison',baseline,capture('h2-standard-after-click','HEADLESS-2'))
|
||||
save(lab,'standard-return',{'status':'STANDARD_RETURN_PASS','workspace':standard_ws,'owner':measure(standard_owner),'scene':standard_scene,'H2_scene':standard_h2,'point':point,'events':events_standard,'pixels':analyze(standard_left),'H2_equal':True})
|
||||
assert all(p.poll() is None for p in [target,ref,filler,pointer])
|
||||
cmd('exit');compositor.wait(timeout=5);assert compositor.returncode==0
|
||||
except Exception as exc:
|
||||
failure=f'{type(exc).__name__}: {exc}';event('failure',message=failure)
|
||||
finally:
|
||||
finished.set();thread.join(timeout=1);stop_owned()
|
||||
for proc in processes:
|
||||
try:proc.wait(timeout=3)
|
||||
except subprocess.TimeoutExpired:
|
||||
os.killpg(proc.pid,signal.SIGKILL);proc.wait(timeout=3)
|
||||
try:os.killpg(proc.pid,signal.SIGKILL)
|
||||
except ProcessLookupError:pass
|
||||
for out in files:out.close()
|
||||
shutil.rmtree(runtime)
|
||||
save(lab,'runtime',{'status':'PASS' if failure is None else 'INCONCLUSIVE','failure':failure,'duration_seconds':time.monotonic()-started,'process_exit_codes':[{'pid':x.pid,'argv':x.args,'exit':x.returncode} for x in processes],'runtime_removed':str(runtime),'cleanup':'owned groups reaped, runtime removed'})
|
||||
damage=[s for s in log('compositor').splitlines() if 'TEMPORAL_DAMAGE ' in s] if (run/'compositor.log').exists() else []
|
||||
save(lab,'damage-analysis',{'extents':damage,'all_frames':'NOT TESTED','PNG_native_buffer_identity':'NOT PROVEN','client_damage':'wl_surface_damage_buffer full SHM at actual commits; see client source/protocol logs'})
|
||||
print('STATIC_FOLLOWUP',failure or 'PASS')
|
||||
if failure:raise SystemExit(1)
|
||||
84
tooling/probes/step20c-static-runtime-followup/support.py
Normal file
84
tooling/probes/step20c-static-runtime-followup/support.py
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
"""Private probe support; no desktop environment or global process ownership.
|
||||
|
||||
WHY: failures must consume the same persistent experiment budget as successes.
|
||||
CONTRACT: a stable flock file serializes durable atomic ledger updates.
|
||||
INVARIANT: no reset/refund; only argv execution and owned process groups.
|
||||
"""
|
||||
import fcntl
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import signal
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
REPO = Path(__file__).resolve().parents[3]
|
||||
LIMITS = {'run': 1, 'capture': 12, 'camera': 1, 'visible_click': 1,
|
||||
'forbidden_click': 1, 'standard_click': 1, 'move': 8}
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
||||
|
||||
def save(lab, name, value):
|
||||
(Path(lab)/'results'/(name+'.json')).write_text(json.dumps(value, indent=2)+'\n')
|
||||
|
||||
def reserve(lab, kind):
|
||||
lab = Path(lab)
|
||||
if kind not in LIMITS:
|
||||
raise ValueError(kind)
|
||||
with (lab/'budget.lock').open('a+') as lock:
|
||||
fcntl.flock(lock, fcntl.LOCK_EX)
|
||||
path = lab/'results/budget.json'
|
||||
state = json.loads(path.read_text()) if path.exists() else {
|
||||
'limits': LIMITS, 'used': dict.fromkeys(LIMITS, 0), 'events': []}
|
||||
if state['limits'] != LIMITS or state['used'][kind] >= LIMITS[kind]:
|
||||
raise RuntimeError('budget exhausted or changed: '+kind)
|
||||
state['used'][kind] += 1
|
||||
state['events'].append({'sequence': len(state['events']), 'kind': kind})
|
||||
fd, temporary = tempfile.mkstemp(prefix='.budget-', dir=path.parent)
|
||||
with os.fdopen(fd, 'w') as out:
|
||||
json.dump(state, out, indent=2); out.write('\n'); out.flush(); os.fsync(out.fileno())
|
||||
os.replace(temporary, path)
|
||||
fd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY)
|
||||
try:
|
||||
os.fsync(fd)
|
||||
finally:
|
||||
os.close(fd)
|
||||
return state
|
||||
|
||||
def environment():
|
||||
env = {k:v for k,v in os.environ.items() if k not in (
|
||||
'SWAYSOCK','I3SOCK','WAYLAND_DISPLAY','DISPLAY','XDG_RUNTIME_DIR',
|
||||
'LD_LIBRARY_PATH','LD_PRELOAD','PKG_CONFIG_PATH')}
|
||||
env.update(LC_ALL='C', PYTHONDONTWRITEBYTECODE='1', MESON_ROOT_CMD='/bin/false')
|
||||
return env
|
||||
|
||||
def command(lab, label, argv, cwd=None, extra=None, timeout=600, allow_failure=False):
|
||||
lab=Path(lab); path=lab/'raw'/(label+'.log')
|
||||
assert not path.exists(), 'diagnostic already recorded: '+label
|
||||
with path.open('wb') as out:
|
||||
proc=subprocess.Popen(list(map(str,argv)),cwd=cwd,env={**environment(),**(extra or {})},
|
||||
stdout=out,stderr=subprocess.STDOUT,start_new_session=True)
|
||||
try:
|
||||
deadline=time.monotonic()+timeout
|
||||
while proc.poll() is None:
|
||||
if time.monotonic()>=deadline or path.stat().st_size>64*1024*1024:
|
||||
raise subprocess.TimeoutExpired(argv,timeout)
|
||||
time.sleep(.1)
|
||||
except subprocess.TimeoutExpired:
|
||||
os.killpg(proc.pid,signal.SIGTERM)
|
||||
try: proc.wait(timeout=3)
|
||||
except subprocess.TimeoutExpired:
|
||||
os.killpg(proc.pid,signal.SIGKILL);proc.wait()
|
||||
try: os.killpg(proc.pid,signal.SIGKILL)
|
||||
except ProcessLookupError: pass
|
||||
raise
|
||||
with (lab/'raw/commands.jsonl').open('a') as out:
|
||||
out.write(json.dumps({'label':label,'argv':list(map(str,argv)), 'cwd':str(cwd),
|
||||
'env':extra or {},'exit':proc.returncode})+'\n')
|
||||
assert path.stat().st_size < 64*1024*1024, 'output bound exceeded'
|
||||
if not allow_failure and proc.returncode:
|
||||
raise RuntimeError(f'{label} exit={proc.returncode}; inspect {path}')
|
||||
return path.read_text(errors='replace')
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
"""Offline exhaustion and cross-process reservation tests, outside campaign LAB."""
|
||||
import concurrent.futures
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
from support import LIMITS, reserve
|
||||
|
||||
def attempt(args):
|
||||
lab, kind = args
|
||||
try: reserve(lab,kind); return True
|
||||
except RuntimeError: return False
|
||||
|
||||
if __name__ == '__main__':
|
||||
with tempfile.TemporaryDirectory(prefix='static-budget-test-') as tmp:
|
||||
lab=Path(tmp);(lab/'results').mkdir()
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as pool:
|
||||
assert sum(pool.map(attempt,[(lab,'capture')]*20))==12
|
||||
for kind,limit in LIMITS.items():
|
||||
if kind=='capture':continue
|
||||
for _ in range(limit):assert attempt((lab,kind))
|
||||
assert not attempt((lab,kind))
|
||||
state=json.loads((lab/'results/budget.json').read_text())
|
||||
assert state['used']==LIMITS
|
||||
assert not attempt((lab,'run'))
|
||||
print('PASS offline ledger: concurrent reservations, exhaustion, durable failures, no refund')
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
"""Exercise scoring against edge differences, actual paint and button events."""
|
||||
from PIL import Image
|
||||
from pixels import analyze, compare, interior, button_pair
|
||||
x=Image.new('RGB',(800,600),(48,48,56));y=x.copy()
|
||||
assert compare(x,y)['equal'];y.putpixel((799,599),(48,48,57))
|
||||
r=compare(x,y);assert not r['equal'] and r['changed']==1 and r['pixels'][0]['x']==799
|
||||
assert analyze(x)['count']==0
|
||||
paint=Image.new('RGB',(800,600),(0,0,0))
|
||||
for xx in range(482,800):
|
||||
for yy in range(25,598):paint.putpixel((xx,yy),(35,68,204))
|
||||
assert analyze(paint)=={'count':182214,'bbox':(482,25,800,598)}
|
||||
assert interior(paint)==(641,311)
|
||||
assert len(button_pair(['BUTTON ns=1 serial=3 button=273 state=1','BUTTON ns=2 serial=4 button=273 state=0']))==2
|
||||
for lines in [[],['ENTER ns=1 x=0 y=0'],['BUTTON ns=1 serial=3 button=273 state=1']]:
|
||||
try:button_pair(lines)
|
||||
except AssertionError:pass
|
||||
else:raise AssertionError('missing delivery accepted')
|
||||
print('PASS exact RGB edge rejection, painted interior and real press/release scoring')
|
||||
Loading…
Reference in a new issue