# Two-axis card entry, synthetic native QEMU On 2026-09-24, `tests/test_card_shell_two_axis_runtime.py` ran the actual cross-built Sway card shell under headless QEMU with two public synthetic Wayland clients (`k230.card.one` blue and `k230.card.two` purple). Sway's test-only native Wayland touch handler injected one contact through the real compositor input and scene paths. No physical board or touch panel was used. Source `532115c23074fdc390e21efd1417d43b7aedc294`; exact build: ```text nix build .#card-shell --max-jobs 1 --cores 4 --no-link --print-out-paths --show-trace /nix/store/516x3gx841c565f5p3ql0jgqwxs02g3i-k230-card-shell ``` Its unwrapped compositor was `/nix/store/ixamm5fj89083y41g55x8p46wfsgbllf-sway-unwrapped-riscv64-unknown-linux-gnu-1.12/bin/sway`. The immutable client was `/nix/store/6nskh3ldk3ya7mwxbb258qszrxhw2i61-card-composition-probe-client-0.1/bin/card-composition-probe-client`. ```sh CARD_SHELL_SWAY=/nix/store/ixamm5fj89083y41g55x8p46wfsgbllf-sway-unwrapped-riscv64-unknown-linux-gnu-1.12/bin/sway \ python3 tests/test_card_shell_two_axis_runtime.py # PASS two-axis app entry: native QEMU pixels, hold/reversal, quick opposite, # privacy and exit; no physical touch # Ran 1 test ... OK ``` The full screenshots were reviewed. On a 100 px upward drag, the blue client mask's lower edge moved from y=1086 to y=976 (110 px; proportional scaling changes the visible edge slightly). At the same vertical position, a 134 px leftward drag moved its right edge from x=504 to x=370, exactly 134 px. A 1 px horizontal bend did not alter vertical bounds; a stationary hold retained the same bounds. Reversal moved the blue mask back before release. The fixture also checked the purple target was visibly raised after release, the opposite bottom gesture returned to blue, the private target exposed only a neutral card before its safe focus restore, and a target that exited during drag was not substituted with another app. The adjacent PNGs are selected unedited headless captures of synthetic apps. Host policy tests separately prove the projected source point follows both finger coordinates exactly before release, bounded edges and release thresholds, snapshotted order, keyboard/app-region exclusion, reduced motion, second-contact cancellation, and disappearing source/target behavior. This does not prove real-glass reachability, touch ownership against actual applications or the keyboard, finger alignment, animation smoothness, or latency. Those physical gates remain **UNVERIFIED**. ## 2026-09-28: vertical-shrink regression, bisection, root cause, and fix The 2026-09-24 evidence above is source `532115c2`. On 2026-09-28 the coordinator reopened task 4.8 after rebuilding `.#card-shell` fresh from the current worktree and rerunning the fixture: it **failed** ```text AssertionError: ((0, 0, 566, 1230), (16, 0, 552, 1128)) ``` at `tests/card_shell_runtime.py:252` (`assert vb[1]>origin[1] and vb[3] `/nix/store/wgzh0z3kxgj41kfgdx5sg1q5syfv0sg1-k230-card-shell`; unwrapped compositor `/nix/store/vr72vwrsgcya09qpannsgbv0339p242s-sway-unwrapped-riscv64-unknown-linux-gnu-1.12/bin/sway`. ```sh CARD_SHELL_SWAY=/nix/store/vr72vwrsgcya09qpannsgbv0339p242s-sway-unwrapped-riscv64-unknown-linux-gnu-1.12/bin/sway \ python3 tests/test_card_shell_two_axis_runtime.py # PASS two-axis app entry: native QEMU pixels, held/reversed quick switch, # direct release, vertical Home settlement, privacy and exit; no physical touch # Ran 1 test ... OK ``` Same 100px upward drag, same measurement the failing assertion uses (`color_box`'s blue mask, `origin`/`vb` from `two-axis-origin.png`/ `two-axis-up.png`): | build | `origin` (pre-touch) | `vb` (after 100px up-drag) | `vb[1]>origin[1]` | `vb[3]0`) | pass | | after fix (`regression-after-fix-up.png`) | `(0, 0, 566, 1230)` | `(30, 26, 536, 1128)` | pass (`26>0`) | pass | `regression-origin.png` is the shared pre-touch frame both rows compare against (identical before/after, since the bug only manifests once the finger drags). The before/after pair was captured back-to-back against the unpatched and patched compositor respectively, same fixture, same 100px drag, same output size. **Full card-shell suites re-verified on the fixed build:** - `python3 tests/test_card_shell_state.py`: 36/36 (host policy driver, `-fsanitize=address,undefined`), including the `entry_finger_y`-based `two-axis-entry`/`tracked-entry` cases that directly assert the anchor-tracking invariant this fix preserves. - `python3 tests/test_card_shell_two_axis_runtime.py`: PASS (above). - `python3 tests/test_card_rounded_clip.py`: PASS, 288 rounded-clip pixel comparisons (host-side render check for the rounded/corner-mask cards this fix must not regress). - `python3 tests/test_card_shell_video_card.py`: PASS (QEMU). - `python3 tests/test_card_shell_switch_neighbour_runtime.py`: PASS (QEMU; exercises the lateral quick-switch path through the same `cs_entry_visual_rect`). - `python3 tests/test_card_shell_scaled_cache_runtime.py`: PASS (QEMU, both cache on/off; exercises the general `CS_ENTERING` carousel path). `tests/test_card_shell_touch_first_runtime.py` and `tests/test_card_shell_home_bleed.py` (the latter's `--two-axis --home-layer-client` case) each failed with a `wait_for(...)`/`restored focus=` timeout during this session, on both the fixed build and the unpatched `88ccca15` build (`/nix/store/j57c5im42d3rq5gxdar2ikmh22717zvc-k230-card-shell`, identical failure) -- pre-existing, reproduced independent of this change. The shared host ran at `load average: 25-62` throughout this session (`~50` concurrent worktrees per `python3 tools/work-status.py`); these two QEMU fixtures' fixed-duration `wait_for`/`time.sleep` assumptions are the most likely explanation, but that is not independently confirmed here and is **UNVERIFIED** as a root cause. Left as a pre-existing, out-of-scope flake for this task; not a regression from this fix. This still does not prove real-glass reachability, touch ownership against actual applications or the keyboard, finger alignment, animation smoothness, or latency. Those physical gates remain **UNVERIFIED**.