Revert the two verify-loop changes: zero matches in four runs

Two changes went in together and the next four runs matched nothing, including
a held press. They cannot be separated after the fact, so both come out and the
loop returns to the shape that has matched every time it was asked to.

One is definitely broken. The rising-edge recapture assumed a frame 50 ms after
detection would show a settled finger; on a quick tap the finger was already
gone, the recapture read the idle floor -- metric 133, still flagged FINGER
from the first capture -- and an empty image went to the matcher. A guaranteed
miss on exactly the case it was meant to fix.

The other is probably wrong. Dropping event 5 as a duplicate rested on
observing that event 7 alone produces a verdict -- but every such observation
was a held frame that followed an event 5 on the same press. Whether the touch
event initialises the press in the trustlet is not known, and five finger
frames with no match is not the evidence to remove it on.

The process error is the one worth writing down: two variables changed at
once, on a live user's finger, with no way to attribute the result. One at a
time from here.
This commit is contained in:
Jorijn van der Graaf 2026-09-02 23:18:42 +02:00
commit 06459a8e73
3 changed files with 31 additions and 28 deletions

View file

@ -1280,20 +1280,19 @@ public:
out.frames++; out.frames++;
std::string note; std::string note;
bool rising = finger && !inPress; // No recapture on the rising edge. It was tried, on the theory that
if (rising) { // the detecting frame is the finger landing and a frame 50 ms later
inPress = true; pressMatched = false; pressRejected = false; // would be a settled one. On a quick tap the finger was already
pressFrames = 0; rescans = 0; pressFid = 0; // gone 50 ms later: the recapture read the idle floor (metric 133,
out.presses++; // still flagged FINGER from the first capture) and an empty image
// The frame that detected the finger is the finger LANDING -- // was reported to the matcher. A guaranteed miss on exactly the
// partial contact, and the frame that rejects most often. // case it was meant to fix.
// Capture once more now that it has settled, so the image the
// matcher sees on the first report is a real one. One capture,
// ~50 ms, on the frame that decides every quick tap.
auto c2 = SendCommand(app_, ta::Cmd::CaptureImage, cap);
if (c2.invoked) { c = c2; note += " recap"; }
}
for (ta::Event ev : tracker.Observe(finger, en::Mode::Authenticate)) { for (ta::Event ev : tracker.Observe(finger, en::Mode::Authenticate)) {
if (ev == ta::Event::FingerTouched) {
inPress = true; pressMatched = false; pressRejected = false;
pressFrames = 0; rescans = 0; pressFid = 0;
out.presses++;
}
std::vector<std::byte> evbuf(ta::EventContextSize); std::vector<std::byte> evbuf(ta::EventContextSize);
ta::BuildEventContext(evbuf, { .event = ev }); ta::BuildEventContext(evbuf, { .event = ev });
// A zero-initialised buffer cannot tell "the matcher never // A zero-initialised buffer cannot tell "the matcher never

View file

@ -88,14 +88,18 @@ export namespace fingerprintd::engine {
// trace. Sending event 7 on every held frame instead feeds the algorithm // trace. Sending event 7 on every held frame instead feeds the algorithm
// near-duplicate images from a single press. // near-duplicate images from a single press.
// //
// Authentication wants event 7, which reaches the matcher unconditionally, // Authentication does want event 7, which reaches the matcher
// and ONLY event 7. Event 5 reaches it too (when device+0x10a8 is 1 or 2, // unconditionally; event 5 only reaches it when device+0x10a8 is 1 or 2.
// which it is here): measured on the daemon, the rising-edge frame sent //
// both and got two verdicts back from one image -- `rej rej`, `-11 -11`, // Both are sent on the rising edge, and that is deliberate. On this device
// `MATCH MATCH`. Each REPORT_EVENT that runs the matcher costs 250-300 ms, // event 5 ALSO runs the matcher, so the rising frame produces two verdicts
// so the second one is a third of a second of redundant work on every // from one image (`rej rej`, `MATCH MATCH`) at ~300 ms each -- and an
// press, on the frame where speed matters most. Enrolment keeps event 5: // attempt to drop event 5 as redundant produced a run with zero matches
// there it is the sample trigger, not a duplicate. // across five finger frames, including a held press. Every match ever
// recorded came after an event 5 on the same press; "event 7 alone
// matches" had only ever been observed on held frames that followed one.
// Whether the touch event initialises the press in the trustlet is not
// known. It is not to be removed without a measurement that isolates it.
class TouchTracker { class TouchTracker {
public: public:
// Returns the events to report for this frame, in order. // Returns the events to report for this frame, in order.
@ -103,7 +107,7 @@ export namespace fingerprintd::engine {
std::vector<Event> out; std::vector<Event> out;
bool rising = finger && !prev_; bool rising = finger && !prev_;
bool falling = !finger && prev_; bool falling = !finger && prev_;
if (rising && mode == Mode::Enrol) if (rising)
out.push_back(Event::FingerTouched); out.push_back(Event::FingerTouched);
if (finger && mode == Mode::Authenticate) if (finger && mode == Mode::Authenticate)
out.push_back(Event::ImageReady); out.push_back(Event::ImageReady);

View file

@ -115,14 +115,14 @@ int main() {
auto e4 = t.Observe(false, Mode::Enrol); auto e4 = t.Observe(false, Mode::Enrol);
Check(e4.empty(), "enrol: idle reports nothing"); Check(e4.empty(), "enrol: idle reports nothing");
// Authentication: every frame with a finger reaches the matcher, ONCE. // Authentication: touch then image-ready on the rising edge. Dropping
// Event 5 also runs the matcher here, so sending it on the rising edge // the touch event as "redundant" (it does also run the matcher) gave a
// produced two verdicts from one image and cost ~300 ms extra on the // run with zero matches; every recorded match followed an event 5 on
// frame where speed matters most. // its press. Not to be removed without a measurement isolating it.
TouchTracker a; TouchTracker a;
auto a1 = a.Observe(true, Mode::Authenticate); auto a1 = a.Observe(true, Mode::Authenticate);
Check(a1.size() == 1 && a1[0] == Event::ImageReady, Check(a1.size() == 2 && a1[0] == Event::FingerTouched && a1[1] == Event::ImageReady,
"auth: rising edge reports image-ready only -- one matcher run, not two"); "auth: rising edge reports touched then image-ready");
auto a2 = a.Observe(true, Mode::Authenticate); auto a2 = a.Observe(true, Mode::Authenticate);
Check(a2.size() == 1 && a2[0] == Event::ImageReady, "auth: a held frame still reports image-ready"); Check(a2.size() == 1 && a2[0] == Event::ImageReady, "auth: a held frame still reports image-ready");
auto a3 = a.Observe(false, Mode::Authenticate); auto a3 = a.Observe(false, Mode::Authenticate);