# STEP20C static R90 partial popup clipping probe **EXPERIMENTAL protocol, before execution.** STEP20C is NEXT; production multi-output Mosaic is NOT YET SUPPORTED. The preceding [rotation probe](STEP20C_ROTATED_OUTPUT_PROBE.md) was PARTIALLY_FEASIBLE: private standard displayed 15,860 B pixels on H2, but Mosaic B ended at PNG x=903, short of the x=1000 boundary. Its pristine R90 fixture stopped after applying transform `90` because it incorrectly expected IPC `270`. This independent campaign tests only a static R90 startup, with no dynamic transform, resize, migration, camera hide/return, effects or algorithm change. ## Fixed geometry and model H1 starts at global (-800,0), physical 750×1000, IPC transform `90`, scale 1.25, logical 800×600. H2 starts at (0,0), physical/logical 800×600, normal at scale 1. Read `get_outputs` before scoring. The expected `grim -s 1.25` H1 PNG is logically oriented 1000×750 according to the earlier probe; its actual dimensions and five distinct markers must be measured anew. The internal Wayland transform enum `270`, physical buffer and logical PNG are different coordinate systems. Pointer injection uses the measured logical union (-800..800, 0..600), with sent x = global x + 800. Use one fixed XDG placement `rot_cross`: TOPLEVEL 420×320; A 480×170 with anchor rect (360,80,1,1); B 300×130 with anchor rect (210,60,1,1). Both anchors fit their parent dimensions. Positioners use TOP_LEFT anchor, BOTTOM_RIGHT gravity and no constraint adjustment requested by the client. Actual `xdg_popup.configure` x/y/w/h, ack, map and scene placement must be recorded; compositor unconstrain may change them. No alternate placement is authorized in this campaign. The prior applied Mosaic owner plus camera 640 placed B's right edge near global -78 (PNG x=903). Moving A's anchor +160 and B's -10 predicts B's right edge near +72, enough for a discriminating H2 portion. A's width adds 180, predicting an A right edge near +42. These are **predictions**, not observations. The original `(390,80)` candidate would shift B by +180: the earlier standard B left edge near -178 would move to +2, removing its H1 portion. This single pre-run variant shifts B by +150 and predicts standard B left near -28, retaining 28 logical pixels on H1. The owner, workspace, applied camera and popup root scene coordinates must be measured separately per mode. Compute each global popup box from the applied owner/scene origin and configured XDG offsets, then intersect with H1 [-800,0)×[0,600) and H2 [0,800)×[0,600). Partial clipping is exercised only when left < 0 < right and both intersections are nonempty. A crossing less than 16 logical pixels is reported with its actual width before qualification. A box wholly within H1 is NOT EXERCISED, even when H2 contains zero popup pixels. ## Runs, observations and decisions One run each: C0 pristine pinned SwayFX `splith`, C1 private `splith`, C2 same private compositor `mosaic`. Static output config is applied at startup. Each run uses one 0700 runtime and explicit private IPC socket, at most two distinct A/B grab chains, 16 captures and 120 seconds. The entire campaign allows at most 3 runs and 50 captures; failed reservations count. A persistent exclusive ledger is tested offline and reserved before each launch. A single initial Mosaic scroll to 640 is permitted before popup creation, logged once. Capture a five-marker H1 background and stable H2 reference before popup creation. Record capture monotonic windows, SHA-256, mode/scale/transform, logical rect, PNG dimensions, popup boxes and clips. Compare H2's entire forbidden intersection to its predeclared reference as well as exact A/B colors, without post-hoc ROI exclusions. At least one standard control must show B pixels on both outputs and a real B button on H2. Mosaic must show its own geometric B crossing, B pixels and a delivered button inside H1, zero B contribution and no B delivery on H2. Derive boundary y from the applied popup box and inspect x=-3..3. Keep hover probes in one chain; use a second chain for an exterior click that may dismiss the grab. A is evaluated separately at points outside B when its own box crosses. The client must log a real button serial on TOPLEVEL before A's grab and on A before B's grab, each configure/ack/map and protocol error. The runner records applied tiled/root/child clips, owner output, damage in buffer-local physical coordinates, commits/presents and actual client delivery. Damage from the inherited `TEMPORAL_DAMAGE` path is already physical: do not apply scale twice. Settled PNG differences do not prove every intermediate frame or an atomic render/input generation. FEASIBLE requires valid crossing B in Mosaic, standard overflow controls, retained H1 pixels/input, excluded H2 pixels/input, correct owner clips and completed pristine R90. PARTIALLY_FEASIBLE preserves a crossing with missing secondary proof. INCONCLUSIVE covers absent crossing, invalid fixture or incomparable controls. BLOCKED requires a reproducible forbidden pixel/hit, wrong owner clip, crash or standard regression. A confirmed leak stops the affected campaign; no clipping algorithm is changed to obtain a pass. R270, saved buffers, optimized blur cache, inner shadow, raw framebuffer, all intermediate frames and ABI/package maintenance remain NOT TESTED. The [source fixture](../../tooling/probes/step20c-rotated-partial-popup/README.md) retains only reviewed scripts and the client placement patch. Generated builds, binaries, captures, sockets, logs and structured results stay in a private LAB. ## Measured static-R90 campaign — 2026-10-12 **EXPERIMENTAL / PARTIALLY_FEASIBLE.** LAB: `/tmp/swayfx-mosaic-nested-grab-PioJ5X43`. The new preparer rebuilt the pinned pristine SwayFX, private Mosaic/SceneFX and strict C17 clients from the prior published source chain, then applied only the dedicated `rot_cross` client patch. The clipping algorithm and installed libraries were untouched. One main run each of pristine `splith`, private `splith` and private `mosaic` completed normally, with 14 captures and about 14.6 seconds per run. The new locked ledger consumed **3/3** runs; dynamic transitions, migrations, resize, camera hide/return and R270 each consumed **0**. Mosaic made one pre-popup static camera placement to scroll 640, recorded in the raw events. All three `get_outputs` states reported H1 physical 750×1000, IPC transform `90`, scale 1.25 and logical (-800,0,800,600). H2 remained 800×600, normal/scale 1 at (0,0). Every H1 `grim -s 1.25` PNG was 1000×750 and the five marker centers established the logical PNG orientation. The control pristine therefore completed the R90 capture and input matrix missed by the historical fixture; no dynamic R90→R0 action occurred. Its two grabs used real button serials 121/126, configured, acked, mapped, and exited without protocol error. Private standard used 123/128; Mosaic used 105/110. The configured XDG offsets were A (360,80,480,170) and B (210,60,300,130) in all modes, matching the fixed legal 1×1 anchors. The actual source scene and owner positions produced these global half-open boxes: | Mode | Applied owner x / camera | A global | A∩H1 / A∩H2 widths | B global | B∩H1 / B∩H2 widths | | --- | --- | --- | --- | --- | --- | | Pristine `splith` | -600 / 0 | [-238,242)×[105,275) | 238 / 242 | [-28,272)×[165,295) | 28 / 272 | | Private `splith` | -600 / 0 | [-238,242)×[105,275) | 238 / 242 | [-28,272)×[165,295) | 28 / 272 | | Private Mosaic | -160 / 640 | [-438,42)×[105,275) | 438 / 42 | [-228,72)×[165,295) | 228 / 72 | The Mosaic owner current rect was x=-160 with width 640, while its applied scene origin was x=-800 after scroll 640. Its content/window x offset was 2, giving root-popup global (-798,25). A starts there plus configured (360,80), B starts at A plus configured (210,60). The sampled `DO_STATE` reported internal transform 3 (`WL_OUTPUT_TRANSFORM_270`), physical 750×1000 and logical 800×600. `PI_SCENE` held tile global -1440 and local clip (640,0,800,600), hence viewport [-800,0)×[0,600). `DO_POPUP` identified owner 8 on H1 and local root clip (-2,-25,800,600), which yields the same global viewport. B remained a scene descendant of A and inherited that root clip. The scene and configured XDG offsets agree at the sampled applied state; this does not establish an atomic generation over all frames. The predefined reference was each mode's `right-reference.png`, captured before A/B creation. At the mapped-B capture, pristine and private standard each had **5,705 exact B pixels on H1** and **35,360 on H2**; their H2 full-image difference from reference contained **49,880 pixels**, with box [0,105,272,295). Mosaic retained **46,455 B pixels on H1**, including 163/489/815 pixels in its last 1/3/5 PNG columns, and **zero B pixels on H2**. Its H2 whole PNG was **pixel-identical** to the predeclared reference: zero differing pixels, not merely zero exact SHM colors. A itself also crossed: Mosaic showed 77,181 A pixels on H1 and zero on H2; the standards showed 58,431 on H1 and 14,520 on H2. The A count includes occlusion by B where their rectangles overlap; it is not a full theoretical A-area count. The measured B geometry, unlike the old probe, genuinely challenged the clip by 72 logical pixels on H2. For B's y=230 boundary grid, the pristine and private-standard clients reported B local motions x=25..31 for global x=-3..3 and delivered right buttons on B at (-14,230) and (30,230). The Mosaic client reported B local motions x=225,226,227 at global x=-3,-2,-1, then a B leave at x=0; no B enter/motion followed for x=0..3. A right button at (-30,230) was delivered to B with serial 119. At forbidden (30,230), B received **no button**; the valid grab produced `popup_done` for B and A. The button on visible A that created B was genuinely delivered (serial 110). An independent click on the clipped-away portion of **A** was not made; P2 root-popup input is therefore PARTIAL even though A's geometry and pixels cross. No universal click-through contract is inferred from the dismissal. Available `TEMPORAL_DAMAGE` extents for the settled reference→mapped-B Mosaic H1 pair covered all **123,636 changed logical-PNG pixels** after mapping `(x,y)` to physical `(749-y,x)`: 10 physical extents, zero uncovered. H2 changed zero pixels. The private-standard pair likewise had zero uncovered changed pixels on both outputs (10 H1 and 6 H2 extents). The pristine control had no passive damage trace; its nonzero screenshot diff cannot be scored against absent extents. These broad damage observations cover only the settled pair, not minimal invalidation, all intermediate frames, saved buffers or render/input generation identity. Private Mosaic logged 126 native commits and 124 presents during the whole run; private standard logged 119/117. Startup, pointer and teardown activity are included. `LAB/results/` retains geometry, positions/intersections, output transform, grab, applied clip, input, pixels, damage, matrix and summary JSON. Raw client/protocol, compositor, pointer, `get_outputs`/tree, PNG and capture windows remain under each `LAB/runs/` directory. The final source and evidence manifest entry counts and SHA-256 values are recorded below after documentation synchronization: **910 source entries**, `3e6930c46dd9abf50aa2720357ccbdd47ac3fdd0f1106a47734383cb281b41a5`; **146 evidence entries**, `e3c13e4fc02f6b66b59b87407ef7b91f5b7cd67430cc1142252098ef3885a949`. The dedicated placement patch SHA-256 is `0f45d48625c062ca05f36849068aedecd02e4480a98f3e0bafe98c1f1c529089`, based on nested client SHA-256 `2e40c98af72a0df8b316810589a0be8f4425a763db1de448b49a7813956b6f7d`. Both manifest files passed entry-by-entry verification after the fixture README was synchronized and the CLI limits were made non-overridable above 120 seconds / 16 captures / 30 MB. Each run retains its executed `fixture-source.py`; that exact source precedes this post-run limit hardening. The hardening was tested offline with an over-limit argument and caused no fourth run or ledger change. The ledger is separate from the historical 5/6 rotation and 1/1 camera budget, which were not reopened. Every run used a private 0700 runtime and explicit private IPC; exit status and cleanup were retained. No production, package or UX change was made. **Verdict:** `SWAYFX_MOSAIC_ROTATED_PARTIAL_POPUP_CLIP_PROBE=PARTIALLY_FEASIBLE`. The central B crossing/visible/clipped/input distinction passed this one static-R90 matrix, including a complete pristine control. The scored clipped-away A input point, all-frame damage and applied generation are still open. Architectural conclusion: `ROTATED_PARTIAL_POPUP_CLIP_FEASIBLE_FOR_TESTED_MATRIX`, without production STEP20C qualification. Saved transaction buffers are the next priority: they can reintroduce pixels from an earlier scene at the same viewport even when the live popup tree clips correctly. Optimized blur cache, interior shadow and SceneFX/wlroots ABI/package maintenance remain separate gates.