evidence · 10.3 KiB

README.md

Committed at docs/evidence/card-shell/app-switch-swipe/README.md. This is what grounds the requirements that cite it.

Open raw source

Fixing the bottom-edge app-switch swipe (real-finger report)

Real-finger board report: swiping left/right along the bottom to switch apps needed far more travel than it looked like, momentum was ignored, and an 80%+ swipe still snapped back to the current app. This is a follow-up fix to the same-day two-axis entry work in the-handheld-presents-a-coherent-shell (design.md decision 11); see decision 12 there for the amended design record.

Root causes, confirmed against the existing test suite

Both are in nix/card-shell-policy/card-shell-policy.c, function cs_entry_up_at (the release decision for the bottom-edge entry/app-switch gesture used from nix/card-shell/adapter.c).

1. A “central quick-switch band” gate, keyed on touch-DOWN x position. cs_begin_entry recorded entry_quick_allowed = x>=width*.25 && x<width*.75 at the moment of touch-down, and the release decision was if (lateral && (vertical || entry_quick_allowed)) target=.... A pure horizontal swipe starting outside the central 50% of the screen width additionally required the same contact to also travel entry_distance (72px) upward — something a deliberately horizontal swipe does not produce. So a swipe starting near either side of the bottom edge reversed regardless of how far it travelled sideways. This was already reproducible in the existing host test suite before this fix: tests/card_shell_policy_driver.c two_axis_entry’s “Side start is not the central quick-switch region” case (x=80, dx=130, i.e. 23% of the 568px panel width) asserted entry_reversing as the expected behaviour. That test now asserts the opposite (a switch), and was verified failing against the pre-fix source (see below).

2. Release velocity was a single last-interval delta, valid for only 80ms after the last recorded sample. cs_entry_motion updated entry_velocity_x/entry_velocity_progress from only the immediately preceding sample (dt>=4 gate, no history), and cs_entry_up_at zeroed it unless time_ms - entry_sample_ms <= 80. Real touch reports arrive roughly every 8ms while the finger is moving continuously (docs/evidence/touch-reports.md, docs/evidence/touch-evtest.txt: SYN_REPORT intervals of ~8.1ms during a drag, e.g. 236.696828 -> 236.704991 -> ...), but the last sample before a release can legitimately be older than 80ms relative to the release timestamp — a naturally decelerating final sample, a slightly delayed release dispatch, or a sparser cadence under load all zero the momentum this way, discarding a flick the finger clearly made.

Fix

nix/card-shell-policy/card-shell-policy.c / .h:

tests/card_shell_policy_driver.c / tests/test_card_shell_state.py:

Confirming the bug and the fix (host, tests/test_card_shell_state.py)

Fresh worktree checkout, plain cc with the exact flags tests/test_card_shell_state.py uses (-std=c11 -Wall -Wextra -Werror -pedantic -g -fsanitize=address,undefined):

cc -std=c11 -Wall -Wextra -Werror -pedantic -g -fsanitize=address,undefined \
  -fno-omit-frame-pointer -I /tmp/k230-as-baseline \
  tests/card_shell_policy_driver.c /tmp/k230-as-baseline/card-shell-policy.c \
  -lm -o /tmp/k230-as-policy-baseline   # /tmp/k230-as-baseline = origin/master's .c/.h
ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 /tmp/k230-as-policy-baseline app-switch-swipe
# Assertion `up_result.consumed && p.entry_settling && p.entry_target_id==101' failed. (FAILS pre-fix)
ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 /tmp/k230-as-policy-baseline two-axis-entry
# Assertion `... p.entry_settling && p.entry_target_id==101' failed. (FAILS pre-fix, root cause 1)

A standalone reproduction of root cause 2 alone (fast drag, then a release 100ms after the last motion sample): pre-fix source returns target=0, reversing=1; this fix returns target=101, settling=1, same raw_dx=120 in both.

Post-fix, full suite:

python3 tests/test_card_shell_state.py -v
# Ran 29 tests in 2.024s -- OK (includes the new app-switch-swipe case)

QEMU proof (real cross-built Sway/wlroots, injected touch; not physical)

nix build --no-link --print-out-paths --max-jobs 1 --cores 6 .#card-shell
# /nix/store/rlq5hkf01gd8hcfmfidzqqp2jg215c9v-k230-card-shell (riscv64, fresh
# rebuild of sway-unwrapped/sway/card-shell/k230-card-shell -- 5 derivations)
nix build --no-link --print-out-paths --max-jobs 1 --cores 6 --impure --expr \
  'let f=builtins.getFlake (toString ./.); p=import f.inputs.nixpkgs {system=builtins.currentSystem;}; \
   in p.callPackage (f.outPath + "/nix/card-composition-probe-client") {}'
# /nix/store/r0i7x90xbk8hnnz081ffkrzgqqbkmiwl-card-composition-probe-client-0.1

CARD_SHELL_SWAY=/nix/store/66dk7fsb3kgfw6mg4xqk2m7h4qhsgq7z-sway-unwrapped-riscv64-unknown-linux-gnu-1.12/bin/sway \
CARD_SHELL_CLIENT=/nix/store/r0i7x90xbk8hnnz081ffkrzgqqbkmiwl-card-composition-probe-client-0.1/bin/card-composition-probe-client \
python3 tests/test_card_shell_two_axis_runtime.py -v   # PASS (same-contact scene, quick switch, vertical Home, privacy, exit)
python3 tests/test_card_shell_gestures.py              # PASS (17 checks, unchanged)
python3 tests/test_card_shell_recovery.py              # PASS (unchanged)
python3 tests/test_card_shell_touch_first_runtime.py   # PASS (unchanged)
python3 tests/test_card_touch_clock.py -v              # PASS (unchanged)
python3 tests/test_card_touch_routing.py --sway <unwrapped sway>  # PASS, 7 checks (unchanged)

evidence_class on every runtime result remains headless-qemu-injected-input / native-wayland-receiver-headless-injected-device. This exercises the real compositor, real wlroots touch routing and the real scene under QEMU user-mode emulation; it is not the panel, not the GT9895 touch controller, and not a real finger.

python3 tools/blob-scan.py exit status: 0 (“every binary is accounted for”).

What remains open

This change has no board/panel/touch-controller evidence and makes no such claim. Everything above is host-ASan proof and QEMU-injected-touch proof.

Real-glass check for the user (operator command): on the board, from a running app, place a finger anywhere along the bottom ~48px edge band (not just the screen’s central half) and swipe left or right. It should switch to the adjacent app either when the swipe covers roughly 30% of the panel’s 568px width (~170px), or on a shorter, decisive flick, with the app tracking the finger 1:1 while held and coasting into place (no snap, no dead stop) on release; a flick back toward the start should still cancel. Starting with an upward component and curving sideways in the same touch should behave identically. A pure vertical swipe should still reach the card overview. Capture with the existing card-shell board harness, e.g.:

python3 tools/capture-feature.py coherent-two-axis --provenance real-touch \
  --duration 60 --description 'Bottom-edge app-switch swipe after the \
  distance/velocity fix' --output-dir docs/evidence/coherent-two-axis

(board task 5.7 in the-handheld-presents-a-coherent-shell/tasks.md, still open).