#!/bin/sh # fplearn.sh -- the template-learning measurement, as a protocol rather than a # pile of remembered commands. # # fplearn.sh wipe remove every stored template (backup first) # fplearn.sh enrol [finger] enrol at the config's sample count # fplearn.sh base [n] [w] trial with learning OFF (the baseline) # fplearn.sh trend [n] [w] three trials with learning ON # fplearn.sh sizes just print the template container sizes # # WIPE FIRST when re-enrolling a finger that is already in the group. Measured # 2026-09-04: the duplicated-finger check refuses a finger the group already # holds -- 0 accepted of 7 presses, rc=0 on every one, while a never-enrolled # finger progressed normally. Re-enrolment ADDS a template and there is no # trustlet-side remove, so the old one has to go first. `enrol` refuses a # finger name that is already in the map and points here. # # WHY IT IS SHAPED LIKE THIS. Learning is cumulative: every matched press folds # frames into the stored template, so a second run is not a repeat of the first # and an A/B against a moving template is not an A/B at all. The only honest # comparison is on ONE template lineage, in order: # # 1. enrol a fresh template, 20 samples, no position prompts -- # which is also the outstanding replication of the 7/10 # result, the one measurement this lane was parked on # 2. base learning off: the clean number for THIS template, and # the only figure comparable to every rate in the journal # 3. trend learning on, three times: the rate should climb, and # the container size is an independent witness that it # is the template moving and not the weather # # Do NOT read run 1 of the trend as "learning made it better". Run 1 starts on # the same template the baseline ended on; it is the first run that can improve # it, not one that has already been improved. set -u GROUP=/mnt/persist/data/RIY7A+mQm3EA4FsCUmkJo0b9dFUYP2YZ4P5hmMiZgeA_Alt MAP=/var/lib/fingerprintd/fingers-10000.map UNIT=fingerprintd-test BIN=/tmp/fingerprintd COMMON="--daemon --verbose --edge-wake --sfs-root=/var/lib/fingerprintd/sfs --sfs-writable --rpmb-write" sizes() { # A template is stored twice, the container and its backup, so the sizes # come in pairs. The BODY is the container minus its 4096-byte header. sudo ls -la "$GROUP" 2>/dev/null | awk '$5 > 200000 { printf " %9d body %9d %s\n", $5, $5-4096, $9 }' | sort -u } restart() { # $1 = extra args sudo systemctl stop "$UNIT" 2>/dev/null sleep 2 sudo systemd-run --unit="$UNIT" --collect $BIN $COMMON $1 >/dev/null 2>&1 printf 'daemon starting' i=0 while [ $i -lt 40 ]; do if busctl --system list 2>/dev/null | grep -q net.reactivated.Fprint; then # Owning the name is not the same as being ready: the session comes # up on the worker thread afterwards. sleep 6; printf ' ready\n'; return 0 fi printf '.'; sleep 1; i=$((i+1)) done printf ' TIMED OUT\n'; return 1 } case "${1:-}" in sizes) echo "template containers now:"; sizes ;; wipe) # The procedure of 2026-09-03 and 2026-09-04, and its reasoning: ONLY the # template containers go (>200000 bytes; each template is stored twice). # The 1588-byte group index is KEPT -- deleting it risks the RPMB # anti-rollback counters going stale against object ids QTEE would then # recreate, which already cost an index restore once -- and a missing # template container is the known-good "not exist, skip" case. The daemon # is stopped first so the trustlet reloads from the store, and a backup is # taken first because a template is not reproducible without a finger. echo "=== wipe: every stored template ===" echo "before:"; sizes D=$HOME/fp6-backups/$(date +%Y-%m-%d-%H%M)-pre-wipe mkdir -p "$D" sudo tar -cf "$D/persist-data.tar" -C /mnt/persist data sudo cp "$MAP" "$D/" 2>/dev/null || true sudo chown -R "$(id -u):$(id -g)" "$D" echo "backup: $D ($(tar -tf "$D/persist-data.tar" | wc -l) entries, $(sha256sum "$D/persist-data.tar" | cut -c1-16))" sudo systemctl stop "$UNIT" 2>/dev/null; sleep 2 n=0 for f in $(sudo ls "$GROUP"); do sz=$(sudo stat -c %s "$GROUP/$f") if [ "$sz" -gt 200000 ]; then sudo rm -f "$GROUP/$f"; n=$((n+1)); fi done sudo sync sudo sh -c ": > $MAP" echo "removed $n container(s); index and small containers kept; map cleared" restart "--learn=1" || exit 1 fprintd-list user 2>&1 | tail -1 echo "after:"; sizes echo; echo "next: fplearn.sh enrol " ;; enrol) F=${2:-right-middle-finger} if sudo grep -q "^$F " "$MAP" 2>/dev/null; then echo "$F is already enrolled (in $MAP)." echo "The trustlet REFUSES to re-enrol a finger it already holds; run" echo " fplearn.sh wipe" echo "first. (Measured 2026-09-04: 0 accepted of 7 presses, rc=0 each.)" exit 1 fi echo "=== enrol $F ===" echo "sizes before:"; sizes restart "--learn=1" || exit 1 /tmp/fpenrol.sh "$F" echo; echo "sizes after the enrolment:"; sizes echo; echo "next: fplearn.sh base" ;; base) N=${2:-10}; W=${3:-5} echo "=== BASELINE: learning OFF ===" restart "--learn=0" || exit 1 echo "sizes before:"; sizes /tmp/fptrial.sh "$N" "$W" echo; echo "sizes after (MUST be unchanged -- learning was off):"; sizes echo; echo "next: fplearn.sh trend" ;; trend) N=${2:-10}; W=${3:-5} echo "=== TREND: learning ON, three runs on one template ===" restart "--learn=1" || exit 1 echo "sizes at the start:"; sizes r=1 while [ $r -le 3 ]; do echo; echo "----- learning run $r of 3 -----" /tmp/fptrial.sh "$N" "$W" echo "sizes after run $r:"; sizes r=$((r+1)) done echo echo "Read it as a trend, not three numbers. A rate that climbs while the" echo "container grows is learning; a rate that moves while the container" echo "does not is noise, and the daemon's own 'learn:' lines say which." ;; *) sed -n '2,38p' "$0"; exit 1 ;; esac