From 9fbf5e6c7364bcd95df502ce2db3d2f9d250c9ac Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Wed, 2 Sep 2026 22:36:47 +0200 Subject: [PATCH] Run the frame loop at 40 ms, and time it 500 ms between frames was the research harness's pace, chosen so a person could read the transcript as it scrolled. It became the daemon's pace by inheritance, not by decision, and it is the root of both complaints from real use: false negatives and a slow answer after lifting. The matcher rejects the early frames of a correct press and matches several frames in -- frames 3 and 8 in the acceptance run. So the number of frames a press gets is what decides it, and at 500 ms a one-second press gets two. And a lift is only noticed on the next frame, so the gap is also the latency a user feels before verify-no-match. A frame costs four QTEE round trips whatever the gap; the gap is pure delay on top. 40 ms is a starting point, not a measurement: the loop now logs milliseconds per frame so the real achievable rate is read rather than assumed, and logs the IRQ line alongside the metric so it is visible whether gpio75 tracks the finger under an armed session -- if it does, lift detection can become an edge wait instead of a poll. --frame-gap= overrides it. --- implementations/main.cpp | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/implementations/main.cpp b/implementations/main.cpp index d8abc3c..be86315 100644 --- a/implementations/main.cpp +++ b/implementations/main.cpp @@ -68,7 +68,14 @@ namespace { constexpr const char* Version = "0.1.0"; bool g_verbose = false; -int g_frameGapMs = 500; +// 500 ms was the research harness's pace, chosen so a human could read the +// transcript scroll by. It is not a design. The matcher rejects the early +// frames of a correct press and matches several frames in (frames 3 and 8 in +// the acceptance run), so frames-per-press is what decides a press -- and a +// lift is only noticed on the NEXT frame, so it is also the latency a user +// feels. A frame costs four QTEE round trips regardless; the gap on top is +// pure delay. +int g_frameGapMs = 40; int g_samples = 10; // common.max_enrolling_samples, as shipped std::string g_logDir = "/var/log/fingerprintd"; std::string g_stateDir = "/var/lib/fingerprintd"; @@ -1242,6 +1249,11 @@ public: bool inPress = false, pressMatched = false, pressRejected = false; int pressFrames = 0, rescans = 0; std::uint32_t pressFid = 0; + auto t0 = std::chrono::steady_clock::now(); + auto msSince = [&](auto t) { + return std::chrono::duration_cast( + std::chrono::steady_clock::now() - t).count(); + }; // The reference frame loop is {QUERY, CAPTURE, REPORT, QUERY, REPORT}. // The trailing query acknowledges the trustlet's event state; without @@ -1300,13 +1312,22 @@ public: out.decided = true; out.matched = true; out.fid = pressFid; std::println(" press {}: {} frames -> MATCH fid={}", out.presses, pressFrames, pressFid); } - if (g_verbose) - std::println(" frame {:3}: metric={:<4}{}{}", i + 1, c.metric, - finger ? " FINGER" : " ", note); SendCommand(app_, ta::Cmd::QueryEventStatus, q); + if (g_verbose) { + // The IRQ line alongside the metric: if it tracks the finger + // under an armed session, lift detection can become an edge + // wait instead of a poll. + auto irq = sensor_.ReadIrq(); + std::println(" frame {:3} @{:5}ms: metric={:<4}{} irq={}{}", i + 1, + msSince(t0), c.metric, finger ? " FINGER" : " ", + irq ? std::to_string(*irq) : "?", note); + } std::this_thread::sleep_for(std::chrono::milliseconds(g_frameGapMs)); } fingerPresent_.store(false); + std::println(" verify loop: {} frames in {} ms ({} ms/frame incl. {} ms gap)", + out.frames, msSince(t0), + out.frames ? msSince(t0) / out.frames : 0, g_frameGapMs); if (cancel) { SendCommand(app_, ta::Cmd::Cancel, {}); out.cancelled = true; @@ -2006,6 +2027,7 @@ int main(int argc, char** argv) { if (a == "--enrol") { doEnrol = true; probe = true; } if (a == "--cal-save") { doCalSave = true; probe = true; g_verbose = true; } if (a.starts_with("--frames=")) frames = std::stoi(std::string(a.substr(9))); + if (a.starts_with("--frame-gap=")) g_frameGapMs = std::stoi(std::string(a.substr(12))); if (a.starts_with("--log-dir=")) g_logDir = a.substr(10); if (a.starts_with("--state-dir=")) g_stateDir = a.substr(12); if (a.starts_with("--rescan=")) g_rescan = std::stoi(std::string(a.substr(9)));