Answer a failing press while the finger is still on the sensor
Jorijn asked whether the timeouts were the wrong-finger path or something to do with learning. Neither. It is a deadlock between the press rule and the instruction the user is given. A press was judged only when the finger was released. The user is told to hold until it answers. So a press whose frames keep rejecting is never released, the daemon never judges it, and the client sits there until its own timeout expires. The transcript shows exactly that: twenty-nine consecutive full-contact frames rejected across twenty seconds with the finger down throughout, surfaced to the user as a hang rather than as a failure. Detection was fine, the settled gate was fine, the matcher was doing its job and saying no. Nothing was ever going to tell the user so. A press now answers no-match once it has accumulated five rejected frames, without waiting for a release. At roughly seven hundred milliseconds a frame that is about three and a half seconds -- long enough that the frame-3 and frame-8 matches in this project's records still land, short enough to be an answer instead of a wait. A phone tells you it did not recognise you while your finger is still on it.
This commit is contained in:
parent
30bb987c27
commit
ae2f537a40
1 changed files with 31 additions and 3 deletions
|
|
@ -98,6 +98,21 @@ int g_rescan = -1; // -1 = leave the config's value alone
|
||||||
// reached a verdict is also reported as no-match. Under rescan=0 this cannot
|
// reached a verdict is also reported as no-match. Under rescan=0 this cannot
|
||||||
// arise (every frame is terminal), so the knob only matters with a budget.
|
// arise (every frame is terminal), so the knob only matters with a budget.
|
||||||
bool g_undecidedIsNoMatch = false;
|
bool g_undecidedIsNoMatch = false;
|
||||||
|
// How many rejected frames one press may accumulate before the daemon calls it
|
||||||
|
// and answers no-match, WITHOUT waiting for the finger to lift.
|
||||||
|
//
|
||||||
|
// Judging a press only at release deadlocks against the instruction the user is
|
||||||
|
// given. Told to hold until it answers, a user holds; a press whose frames keep
|
||||||
|
// rejecting is never released, so the daemon never judges it and the client sits
|
||||||
|
// there until its own timeout. Measured 2026-09-05: 29 consecutive full-contact
|
||||||
|
// frames rejected over 20 seconds with the finger down the whole time, reported
|
||||||
|
// to the user as a hang rather than a failure.
|
||||||
|
//
|
||||||
|
// A phone tells you it did not recognise you while your finger is still on it.
|
||||||
|
// At ~700 ms a frame, five is about three and a half seconds -- long enough that
|
||||||
|
// the frame-3 and frame-8 matches in this project's records still land, short
|
||||||
|
// enough to be an answer rather than a wait.
|
||||||
|
int g_pressRejectBudget = 5;
|
||||||
bool g_irqObserve = false;
|
bool g_irqObserve = false;
|
||||||
bool g_edgeWake = false;
|
bool g_edgeWake = false;
|
||||||
|
|
||||||
|
|
@ -1447,7 +1462,7 @@ public:
|
||||||
en::TouchTracker tracker;
|
en::TouchTracker tracker;
|
||||||
harvested_ = 0; // no fold has happened in this session yet
|
harvested_ = 0; // no fold has happened in this session yet
|
||||||
bool inPress = false, pressMatched = false, pressRejected = false;
|
bool inPress = false, pressMatched = false, pressRejected = false;
|
||||||
int pressFrames = 0, rescans = 0, skipped = 0;
|
int pressFrames = 0, rescans = 0, skipped = 0, pressRejects = 0;
|
||||||
std::uint32_t pressFid = 0;
|
std::uint32_t pressFid = 0;
|
||||||
auto t0 = std::chrono::steady_clock::now();
|
auto t0 = std::chrono::steady_clock::now();
|
||||||
// When contact first appeared. The wall clock a client sees starts when
|
// When contact first appeared. The wall clock a client sees starts when
|
||||||
|
|
@ -1491,7 +1506,7 @@ public:
|
||||||
for (ta::Event ev : tracker.Observe(finger, settled, en::Mode::Authenticate)) {
|
for (ta::Event ev : tracker.Observe(finger, settled, en::Mode::Authenticate)) {
|
||||||
if (ev == ta::Event::FingerTouched) {
|
if (ev == ta::Event::FingerTouched) {
|
||||||
inPress = true; pressMatched = false; pressRejected = false;
|
inPress = true; pressMatched = false; pressRejected = false;
|
||||||
pressFrames = 0; rescans = 0; pressFid = 0;
|
pressFrames = 0; rescans = 0; pressFid = 0; pressRejects = 0;
|
||||||
out.presses++;
|
out.presses++;
|
||||||
}
|
}
|
||||||
std::vector<std::byte> evbuf(ta::EventContextSize);
|
std::vector<std::byte> evbuf(ta::EventContextSize);
|
||||||
|
|
@ -1527,7 +1542,19 @@ public:
|
||||||
note += " folded";
|
note += " folded";
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ta::Verdict::Rejected: pressRejected = true; note += " rej"; break;
|
case ta::Verdict::Rejected:
|
||||||
|
pressRejected = true; ++pressRejects; note += " rej";
|
||||||
|
// Answer while the finger is still down. See
|
||||||
|
// g_pressRejectBudget: waiting for a release the user has
|
||||||
|
// been told not to make is a deadlock, not a policy.
|
||||||
|
if (!pressMatched && g_pressRejectBudget > 0
|
||||||
|
&& pressRejects >= g_pressRejectBudget) {
|
||||||
|
out.decided = true; out.matched = false;
|
||||||
|
std::println(" press {}: {} rejected frame(s) -> NO MATCH "
|
||||||
|
"(budget reached, finger still down)",
|
||||||
|
out.presses, pressRejects);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case ta::Verdict::NotIdentifiedYet: rescans++; note += " -11"; break;
|
case ta::Verdict::NotIdentifiedYet: rescans++; note += " -11"; break;
|
||||||
case ta::Verdict::MatcherNeverRan: break;
|
case ta::Verdict::MatcherNeverRan: break;
|
||||||
}
|
}
|
||||||
|
|
@ -2610,6 +2637,7 @@ int main(int argc, char** argv) {
|
||||||
if (a.starts_with("--log-dir=")) g_logDir = a.substr(10);
|
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("--state-dir=")) g_stateDir = a.substr(12);
|
||||||
if (a.starts_with("--rescan=")) g_rescan = std::stoi(std::string(a.substr(9)));
|
if (a.starts_with("--rescan=")) g_rescan = std::stoi(std::string(a.substr(9)));
|
||||||
|
if (a.starts_with("--reject-budget=")) g_pressRejectBudget = std::stoi(std::string(a.substr(16)));
|
||||||
if (a.starts_with("--group-path=")) g_groupPath = a.substr(13);
|
if (a.starts_with("--group-path=")) g_groupPath = a.substr(13);
|
||||||
if (a.starts_with("--samples=")) { g_samples = std::stoi(std::string(a.substr(10))); g_samplesForced = true; }
|
if (a.starts_with("--samples=")) { g_samples = std::stoi(std::string(a.substr(10))); g_samplesForced = true; }
|
||||||
if (a.starts_with("--learn=")) g_learn = a.substr(8) != "0";
|
if (a.starts_with("--learn=")) g_learn = a.substr(8) != "0";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue