diff --git a/implementations/main.cpp b/implementations/main.cpp index 46a4716..793d51f 100644 --- a/implementations/main.cpp +++ b/implementations/main.cpp @@ -1574,9 +1574,19 @@ public: SendCommand(app_, ta::Cmd::Cancel, {}); out.cancelled = true; } - // The matching frame was already folded, at the moment it matched. - // This picks up any further frames the finger stayed down for. - if (out.matched && g_learn) HarvestTemplate(g_learnMaxFrames); + // NO HARVEST HERE. The matching frame was already folded, at the + // moment it matched, and that one costs a single command. Harvesting + // the REST of the press is up to eight more capture-and-fold round + // trips, and doing it before returning puts all of them on the + // client's latency path -- which is exactly what happened: a press the + // user held cost ~2.7 s more than one they released, entirely because + // holding kept the harvest fed to its frame limit. Measured + // 2026-09-05: held presses folded 8 frames, released presses folded 1. + // + // The save was already deferred for this reason. The harvest has to be + // deferred with it, and the worker runs both once the verdict is on + // its way. A finger held through the verdict is still there a + // millisecond later, so nothing is lost by waiting. return out; } @@ -1925,10 +1935,13 @@ private: else if (o.matched) ev->status = "verify-match"; else ev->status = "verify-no-match"; PostEvent(std::move(ev)); - // AFTER the verdict is on its way to the client, never before: - // persisting a learned template is ~350 ms of gpfile and RPMB - // traffic and it must not sit on the unlock path. Stock defers - // it the same way, with a timer. + // AFTER the verdict is on its way to the client, never before. + // BOTH of these are off the unlock path deliberately: the + // harvest is up to eight capture-and-fold round trips and the + // save is ~350 ms of gpfile and RPMB traffic. Stock defers its + // save the same way, with a timer, and keeps harvesting while + // the finger is down after the framework has been told. + if (o.matched && g_learn) session_.HarvestTemplate(g_learnMaxFrames); session_.FlushTemplate(); break; }