test(pf-vkdecode): diff a field capture against ffmpeg, frame by frame #461

Merged
enricobuehler merged 6 commits from worktree-vkdecode-field-parity into main 2026-08-30 23:36:49 +00:00
6 Commits
Author SHA1 Message Date
enricobuehler 7db513683d Merge main, keeping this branch's App::status() accessor
apple / swift (pull_request) Successful in 2m14s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Successful in 6m36s
android / android (pull_request) Canceled after 0s
ci / rust (pull_request) Canceled after 0s
ci / rust-arm64 (pull_request) Canceled after 0s
ci / web (pull_request) Canceled after 0s
ci / docs-site (pull_request) Canceled after 0s
ci / bun-nix (pull_request) Canceled after 0s
ci / docs-drift (pull_request) Canceled after 0s
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Canceled after 0s
Both this branch and #460 fixed the same abort, independently and within
the hour. #460 merged first, patching four call sites inline; this one
routes all five through one `App::status()`, which is the better shape
and also covers the writer in `run()` that the inline pass left alone.

Conflict resolved to this branch for `win.rs`. Afterwards the file holds
exactly one `lock()` — inside the accessor — so every reader goes
through it. `check-unsafe-hygiene.sh` reports all three gates clean and
rustfmt is clean under the pinned 2021 style edition.
2026-08-31 01:28:18 +02:00
enricobuehler 1ec673add8 fix(tray): a poisoned status lock stops aborting the tray process
ci / bun-nix (pull_request) Successful in 41s
ci / docs-drift (pull_request) Successful in 43s
ci / docs-site (pull_request) Successful in 1m5s
ci / web (pull_request) Successful in 1m23s
ci / rust-arm64 (pull_request) Successful in 1m59s
apple / swift (pull_request) Successful in 2m15s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Successful in 6m45s
ci / rust (pull_request) Failing after 7m32s
android / android (pull_request) Successful in 8m44s
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Successful in 3m6s
`ci / rust` has been red on main: gate B of check-unsafe-hygiene.sh flags
`.unwrap()` on the status mutex inside `wndproc`, and it is right to. A
panic crossing an `extern "system"` boundary does not unwind, it ABORTS
the process — so a thread that panicked while holding this lock would
take down the host's only visible surface, on a box where the icon is
also how the operator stops the host.

Poisoning is not a reason to do that here. What the mutex guards is a
display enum; reading it after another thread died is harmless, and
`PoisonError::into_inner` hands it over — the idiom already used
elsewhere in the tree.

Routed through one `App::status()` rather than patched at the line CI
could see. The gate is lexical, so it flagged only the call written
inline in `wndproc`; the other four sit in helpers that `wndproc` calls,
where a panic unwinds into the very same boundary and aborts just the
same. Fixing only the visible one would have left four identical aborts
a function call away and made the gate look satisfied rather than the
bug fixed.

⚠ NOT verified locally: the tray cannot be type-checked for Windows from
a Mac (aws-lc-sys' build script cross-compiles C and dies in cc-rs long
before win.rs), and scripts/xcheck.sh does not carry punktfunk-tray.
windows-host.yml builds AND clippies this crate and triggers on
`crates/punktfunk-tray/**`, so CI is the verifier here.
2026-08-31 00:40:18 +02:00
enricobuehler 5ee6468b2b test(pf-vkdecode): one lock-proving helper replaces six set_var copies
apple / swift (pull_request) Successful in 2m15s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
ci / rust (pull_request) Failing after 2m29s
ci / bun-nix (pull_request) Successful in 31s
ci / web (pull_request) Successful in 2m53s
ci / docs-site (pull_request) Successful in 2m18s
ci / docs-drift (pull_request) Successful in 1m0s
ci / rust-arm64 (pull_request) Successful in 1m45s
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Successful in 6m44s
android / android (pull_request) Successful in 8m21s
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Successful in 3m14s
CI gate C counts `env::set_var` mentions per file and this file was at
its baseline of five, so the field leg's sixth copy failed the build.
Raising the baseline is what the gate tells you not to do, and it would
have been the wrong answer anyway: the six copies were the same two-line
safety argument written out six times, which is precisely the shape a
ratchet exists to discourage.

`arm_test_readback` takes the GPU lock guard BY REFERENCE. `env::set_var`
is safe to call and unsound from a live multithreaded process, so "the
caller holds the binary-wide lock" is the entire safety argument — and a
borrow states it in the type system, where it cannot drift out of date,
instead of in prose repeated at each call site.

The file drops from six mentions to two (the call, and the doc comment
that names the API it is arguing about — gate C counts comments too, by
design, and writing around its grep would be gaming it). The baseline
moves 5 -> 2 to match, so the ratchet stays tight rather than merely
satisfied.

Gates: fmt, clippy --all-targets and 197 lib tests clean;
check-unsafe-hygiene.sh gate C now passes. Gate B's punktfunk-tray
finding is untouched by this branch and fails identically on main.
2026-08-31 00:17:47 +02:00
enricobuehler 2629508a10 fix(scripts): read framehash's bare hex digest, not a prefix that never existed
ci / bun-nix (pull_request) Successful in 30s
ci / docs-drift (pull_request) Successful in 41s
ci / web (pull_request) Successful in 1m2s
ci / docs-site (pull_request) Successful in 1m3s
ci / rust-arm64 (pull_request) Successful in 1m26s
ci / rust (pull_request) Failing after 4m38s
android / android (pull_request) Successful in 5m28s
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Successful in 10m21s
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Canceled after 3m10s
The extraction matched on `sha256=`, which ffmpeg's framehash muxer
does not emit: its data rows are `0, <dts>, <pts>, <duration>, <size>,
<bare hex>` and the algorithm is named once in a `#hash:` header. So
the reference file came out EMPTY and the script reported "ffmpeg=0
frames" — indistinguishable, to a reader, from ffmpeg having failed to
decode the capture at all. Take the last comma-separated field of every
non-comment row instead.
2026-08-31 00:00:59 +02:00
enricobuehler e7d650f7b6 test(pf-vkdecode): report which field AUs needed concealment
A capture taken over a lossy link holds AUs whose references never
arrived, and ffmpeg conceals those differently from us — so a divergence
at one of them says nothing about this decoder. Recording the AU indices
(and printing the verdict last) is what separates "our bug" from "the
field lost packets", which is the only reading that makes a field diff
actionable. Split out of a dropped commit whose other half was wrong.
2026-08-31 00:00:59 +02:00
enricobuehler dbb9d75718 test(pf-vkdecode): decode a field capture into per-frame hashes
The golden parity legs only decode streams our hosts never emit, and the
field defect they cannot see is exactly the one two Deck reporters now
show: a smear that accumulates through the reference chain while every
integrity counter stays at zero. The full client audit (wire, planner,
params conversion, slots, scope, ring, submission, caps/session) found
no defect by inspection, so the fix needs the first divergent frame off
real silicon.

This leg turns any PUNKTFUNK_DUMP_VIDEO capture into that number:
PF_VKD_FIELD_STREAM points at the .h265 (the .idx sidecar is honoured
when present, complete==0 lines skipped), the join point is found the
way the client found it (plan until an AU succeeds), and every
delivered frame is read back and hashed to <stream>.pfhash in display
order. Per-AU errors are tolerated and counted, off-size frames from a
mid-capture renegotiation are released unshown, and PF_VKD_FIELD_YUV
dumps named frames' planes for eyes-on inspection.

scripts/vkdecode-field-parity.sh is the other half: ffmpeg -f framehash
over the same bytes (nv12/p010le match the readback's packing exactly),
first divergent index, ffprobe facts for the divergent picture.
consume_frame now returns the planes and the hashing moved to its
callers, which is the whole extent of the change to the golden legs.
2026-08-31 00:00:59 +02:00