Add fplearn.sh, because a cumulative measurement is a protocol not three commands

Learning rewrites the template on every matched press, so run two is not a
repeat of run one and an A/B against a moving template is not an A/B. The order
is the measurement:

  enrol   a fresh template at 20 samples with no position prompts, which is also
          the outstanding replication of the best result this lane has seen
  base    learning off: the clean number for that template, and the only figure
          comparable to every rate already recorded
  trend   learning on, three times: the rate should climb, with the container
          size as an independent witness that it is the template moving

A rate that climbs while the body grows is learning. A rate that moves while the
body does not is noise, and the daemon's own learn: lines say which. Run one of
the trend is not evidence that learning helped; it starts on the template the
baseline ended on.

One limit the script states rather than letting someone discover it: learning
only fires on a press that matched, so a template that never matches never
improves. That is why the protocol re-enrols instead of building on a template
already measured at 0 of 10.
This commit is contained in:
Jorijn van der Graaf 2026-09-03 17:46:33 +02:00
commit c5fc3afc77
2 changed files with 123 additions and 0 deletions

View file

@ -109,6 +109,31 @@ the shipped config sets `max_authentication_rescan_times` to 0, because at the
stock budget a wrong finger never yields a terminal frame and a PAM client
waits forever for the `verify-no-match` it needs.
**Template learning.** Stock rewrites the stored template on every successful
press — `0x1015 UPDATE_TEMPLATE` while the finger is still down, then a deferred
`SAVE_DATA` — and the stored body measurably grows over a template's life
(333278 bytes at enrolment to 371734 after one authentication session on the
reference device). This daemon does the same as of 0.1.0. It matters more than
any tuning knob: before it, every match rate measured against this device was
measured against a day-zero template that no stock user lives with.
The harvest sends no event, so the matcher does not re-run and a verdict cannot
be revised by it. A quick tap pays almost nothing, because the finger is gone by
the time a verdict lands; a held press contributes the frames it was held for —
which means the frames learned from are, by construction, frames from real
unlock presses. The save is deferred until after the verdict reaches the client,
because ~350 ms of RPMB traffic does not belong on an unlock path; stock defers
it the same way. `--learn=0` turns the whole thing off so the comparison can be
made on one binary, which is the only way it is single-variable.
**Reading the trustlet.** There is no `tzdbg` on mainline, but focal64 writes
its log into the response buffer, so `--ta-log` surfaces the matcher's own
verdicts (`auth success score`, `identify fail! FtVerifyByTemplate() = -2`, the
per-frame `image quality / coverage / humidity`). The log ring is ~150 lines per
session and is never reset, so the config dump alone can overflow it: the
shipped verbose config leaves the framework log off to reserve the ring for the
algorithm. Lower levels are more verbose and 6 is off.
**Known about the loop, from real use.** A verify frame is four QTEE round
trips (~200 ms idle); a `REPORT_EVENT` that runs the matcher is ~300 ms, and
the rising edge pays it twice because events 5 and 7 both reach the matcher.