Preserve throw velocity across repeated millisecond timestamps
The policy previously set velocity to zero whenever two consecutive motion events had the same millisecond timestamp. A valid upward movement followed by another movement or a duplicate endpoint in that timestamp then failed to request close. This is independently reproduced in the actual C policy and the cross-built compositor’s native wlroots input path.
The policy now measures those samples from the preceding distinct timestamp. Coordinates still update on every event. A same-time downward reversal resets the velocity reference conservatively, and motion with no elapsed interval cannot throw. A later stationary sample, slow drag and held release continue to reject close. The 120-pixel displacement, 0.4-pixel/ms speed and 150-ms release-age thresholds are unchanged, as are dispatch-time close deadlines, graceful close/refusal behavior and performance budgets.
Host and native runtime proof
python3 tests/test_card_shell_state.py
TMPDIR=/mnt/MediaVolume/home/jadams nix build .#card-shell \
--max-jobs 1 --cores 8 --no-link --print-out-paths
python3 tests/card_shell_runtime.py --native-touch --delayed-touch \
--sway /nix/store/d7pxj3jviq5ghax97hy98fnqlmhbiy0f-sway-unwrapped-riscv64-unknown-linux-gnu-1.12/bin/sway \
--client /nix/store/6nskh3ldk3ya7mwxbb258qszrxhw2i61-card-composition-probe-client-0.1/bin/card-composition-probe-client \
--output NEW_HEADLESS_DIRECTORY
All 23 compiled policy cases pass with address/undefined-behavior sanitizers.
Two new case groups cover duplicate and advancing same-time endpoints, a
zero-duration whole movement, same-time reversal, a later stationary sample,
slow movement and held release. The new positive group fails with the original
policy from base 4ba9f58a; its other 22 cases pass.
build.json identifies the built package and source hashes. headless.json
records 17 compositor runtime checks and eight independent source-timing
checks, including repeated-timestamp close and reversal/held-release rejection.
This executes the RISC-V compositor under QEMU user emulation with native
Wayland clients and wlroots/cursor/seat routing, not a full Linux guest.
For the runtime negative control, substitute the previous timestamp-corrected
Sway /nix/store/bggwbi2nx72c494xpjvgyi98yxz3w3c0-sway-unwrapped-riscv64-unknown-linux-gnu-1.12/bin/sway
in the same command. It passes the preceding timing checks, then fails waiting
for the first same-millisecond motion close request. negative-control.json
records that expected exit 1 and the exact runtime-test hash.
Remaining physical gate
This does not establish the cause of the four intermittent upward-throw misses in the RVV comparison. Its input injector’s absolute scheduling deadlines can produce bursts after a delay, but those runs did not retain the individual gesture source timestamps needed for attribution. Three unchanged injected board workload repeats now observe all thirteen interaction checks apiece; see the physical report below. Real-finger acceptance, default-image integration and all failed card cost budgets remain open. The normal image has not changed.
Three physical-board repeats
board/result.json records three unchanged card-shell-acceptance.py runs on
normal system gnr36q…, using published fix revision
33962837dd8a6dfc20d2ee944f321d45808b11df and package mp0bqh… from build.json.
All 39/39 interaction checks were OBSERVED, including each upward-throw
request, refusal, timeout, accepted close and normal-control route. Each run
verified the running Sway executable and restored normal shell/seatd. These
are injected-input observations, not reviewed new pictures or physical fingers.
The root-only bounded runner is committed as run-board.py. Stage it beside
exactly the three published session, acceptance and budget tools; their hashes
are in the board result. The actual command, within a transient root service
with a 1,800-second outer deadline, was:
python3 /var/lib/k230/card-throw-tools-33962837/run-board.py \
--board --revision 33962837dd8a6dfc20d2ee944f321d45808b11df \
--output /var/lib/k230/card-throw-sampling-33962837
Each session had the independent root watchdog, a 540-second compositor limit,
and a 480-second acceptance timeout. The three runs retain the same normal
kernel, panel, Pixman renderer, configuration, client, 24 drags per card count,
and existing benchmark producer. transfer.json records SHA256 verification
of the 23 named text files; raw process logs and native pictures were excluded.
All six workload budgets still FAIL. All reports were reconstructed exactly
from committed telemetry, excluding only the newly generated report timestamp.
comparison.json preserves the per-run summary without a speedup claim.
| Repeat | Cards | Frames | Frame CPU p95 / max ms | Tracking p95 ms |
|---|---|---|---|---|
| 1 | 1 | 144 | 16.574 / 49.312 | 57.475 |
| 1 | 2 | 139 | 23.663 / 25.896 | 57.474 |
| 2 | 1 | 138 | 16.511 / 49.612 | 57.478 |
| 2 | 2 | 143 | 23.726 / 25.411 | 57.478 |
| 3 | 1 | 142 | 16.309 / 48.920 | 57.477 |
| 3 | 2 | 150 | 23.843 / 26.934 | 57.478 |
The unchanged CPU limits are p95 16.667 ms / max 33.334 ms; tracking p95 is 33.334 ms. Frame CPU and tracking fail in every workload; the other declared input/release/memory metrics pass. Reproduce each complete report with:
python3 tools/card-shell-benchmark.py --board \
--input docs/evidence/card-shell/throw-sampling/board/run-1/public/telemetry.log \
--manifest docs/evidence/card-shell/throw-sampling/board/run-1/public/manifest.json \
--output /tmp/card-throw-repeat-1-budget.json
Exit 1 is expected for the retained budget failure; repeat for runs 2 and 3.
normal-restoration.json and its serial record independently check the normal
system, shell, seatd, Wi-Fi HTTPS, protected boot/firmware hashes and root layout.
Its repeat phase compares against the committed storage baseline; no reboot
or flash occurred during this trial, and its boot ID matches the three runs.
Three successful injected repeats support this fix but are not a reliability estimate or a matched before/after board attribution. The previous failures remain committed. Card tasks 4.2, 5.1 and 5.3 stay open: repaint/cadence cost, default-image integration and focused real-finger acceptance still need proof.