diff --git a/interfaces/Fingerprintd-Engine.cppm b/interfaces/Fingerprintd-Engine.cppm index 46e3870..45e6c51 100644 --- a/interfaces/Fingerprintd-Engine.cppm +++ b/interfaces/Fingerprintd-Engine.cppm @@ -42,9 +42,21 @@ export namespace fingerprintd::engine { class Baseline { public: static constexpr std::size_t DefaultSamples = 5; - // A finger reads roughly 2.6x the floor; 2x separates them with margin - // on both measured runs. - static constexpr std::int32_t Multiplier = 2; + + // A finger at full contact reads ~2.7x the floor (133 idle, 355-375 + // pressed), so 2x separated them with margin. But 2x also discards the + // LANDING frames -- measured at 209 and 247 against a 133 floor, with + // the interrupt already asserted -- and those are the only frames a + // quick tap has to spare: a frame that detects a finger takes 400-580 ms + // to process (the trustlet's REPORT_EVENT is ~300 ms of it) while a tap + // lasts 400-600 ms, so detection one frame earlier is the difference + // between one image and two. + // + // 3/2 puts the threshold at 200 for a 133 floor: above the highest idle + // drift observed (147) and below the lowest landing frame seen (209). + // Expressed as a ratio because the floor is calibrated per session. + static constexpr std::int32_t MultiplierNum = 3; + static constexpr std::int32_t MultiplierDen = 2; explicit Baseline(std::size_t samples = DefaultSamples) : want_(samples) {} @@ -59,7 +71,7 @@ export namespace fingerprintd::engine { bool Ready() const { return seen_ >= want_ && floor_ > 0; } std::int32_t Floor() const { return floor_; } - std::int32_t Threshold() const { return floor_ * Multiplier; } + std::int32_t Threshold() const { return floor_ * MultiplierNum / MultiplierDen; } // Nothing is a finger until the floor is known. An uncalibrated // Baseline reports false for everything rather than inventing a diff --git a/tests/Engine/main.cpp b/tests/Engine/main.cpp index e56b2cc..c108ebc 100644 --- a/tests/Engine/main.cpp +++ b/tests/Engine/main.cpp @@ -71,21 +71,28 @@ int main() { Check(!b.Ready(), "uncalibrated"); Check(!b.IsFinger(1000000), "an uncalibrated baseline calls nothing a finger"); - for (int i = 0; i < 5; i++) b.Observe(132); + for (int i = 0; i < 5; i++) b.Observe(133); Check(b.Ready(), "calibrated after five idle samples"); - Check(b.Floor() == 132, "floor"); - Check(b.Threshold() == 264, "threshold is 2x the floor"); + Check(b.Floor() == 133, "floor"); + Check(b.Threshold() == 199, "threshold is 1.5x the floor"); - // The real measurement: idle 132-133, finger 345-366. + // The real measurement: idle 133 with drift to 147, landing frames + // 209-247, full contact 355-375. The threshold has to sit between the + // drift and the landing frames -- catching a landing frame is the only + // way a quick tap gets a second image. Check(!b.IsFinger(133), "an idle frame is not a finger"); - Check(b.IsFinger(345) && b.IsFinger(366), "a pressed frame is"); + Check(!b.IsFinger(147), "the highest observed idle drift is not a finger"); + Check(b.IsFinger(209), "a LANDING frame is a finger (2x would have missed it)"); + Check(b.IsFinger(247), "so is a partial-contact frame"); + Check(b.IsFinger(355) && b.IsFinger(375), "full contact certainly is"); + Check(133 * 2 > 247, "2x would have discarded both landing frames"); // The floor is the max of the idle samples. A drifting idle must not // become a false finger. Baseline drift; for (std::int32_t m : {18, 20, 22, 24, 26}) drift.Observe(m); Check(drift.Floor() == 26, "floor takes the maximum, not the first sample"); - Check(!drift.IsFinger(24), "drift within the idle range is not a finger"); + Check(!drift.IsFinger(26), "drift within the idle range is not a finger"); // Extra samples after calibration do not move it. Baseline fixed;