Put the grouping dials where the regrouping is

The merge probability was `dr_face`'s constant and the smallest group was
a bare `< 2` in the clustering pass. Both were tuned on one library —
1,813 faces of one photographer's family — and the quantity they optimise
is a property of the population, not of the model. A household at close
family resemblance and two thousand strangers at a wedding want different
answers, and neither of them is the reference library. The doc comment
already conceded the point and pointed at `face_index --tune`; a
photographer does not have a terminal.

So they are `FaceSettings` now, saved per device beside the cache budgets
and edited from the People screen — beside the Regroup button that
applies them and the rail that shows what they did, because a value
changed three screens away from its effect is one nobody can tune.

Moving them is safe by construction, which is why nothing asks for
confirmation: a regroup writes only the suggested half, and
confirmations, names and ignores enter as anchors and come back
unchanged. The smallest-group rule is applied only to groups the system
invented — a group the user named or set aside survives it whatever its
size, because a display preference does not overrule a judgement.

**Withdrawal, without which the setting does nothing visible.** Raising
the smallest group stops the pass creating small groups; it does not
remove the ones a previous pass made, because those still hold their
suggestions, so they are not empty, so the prune leaves them. The pass
now releases every unanchored face it did not place before pruning.

And a dial you cannot see the effect of is not a dial. "What would this
do?" runs the same population through the clusterer without opening a
transaction and reports groups, faces grouped and largest group — one row
of `--tune`'s table, on the user's own library, on a worker thread. The
line leads with the group count because that is the number that says
which side of the right setting you are on: it climbs as fragments are
gathered into people and falls as separate people start being welded,
while the grouped-face count rises straight through both.

The preview parks its poll timer in a slot of its own. A preview and a
regroup are allowed to be in flight together, and sharing the sweep's
single slot would have the second to start drop the first's timer —
visible as a Regroup that finished on its worker and never said so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-29 23:18:46 +02:00
co-authored by Claude Opus 5
parent e465ff0c80
commit d0b671d4db
10 changed files with 883 additions and 89 deletions
+34
View File
@@ -788,6 +788,40 @@ It is **not** a merge threshold and must not become one. Uniqueness is relative,
one named person would hand every stray face a 1. "Is this the same person at all" stays §8's
question, and coherence is the half of the product that carries it.
### 9.2 The two numbers the user is allowed to move · 2026-08-29
The merge probability and the smallest group the pass will call a person are `FaceSettings` in
`dr-types`, edited from the People screen and saved per device beside the cache budgets. They were
constants: `dr_face::DEFAULT_MERGE_PROBABILITY` and a bare `< 2` in `dr_ui::faces::recluster`.
**Why they had to become settings.** The default was tuned on one library — the table in
`dr_face::cluster`'s doc comment is 1,813 faces of one photographer's family — and the quantity it
optimises is a property of the population, not of the model. A library of one household at close
family resemblance and a library of two thousand strangers at a wedding want different answers, and
neither of them is the reference library. The doc comment already conceded the point ("this is a
*default*, not a constant of nature") and pointed at `face_index --tune` as the way to find a better
one; a photographer does not have a terminal.
**Why moving them is safe, and why that is the reason there is no confirmation on it.** A regroup
writes only the *suggested* half. Confirmations, names and ignores enter as anchors and come back
unchanged (FR-CULL-10), so the pass is re-runnable by construction and a dial the user can move is
just that property being used. The smallest-group rule is applied only to groups the system invented:
a group carrying a person — confirmed, named or set aside — survives it whatever its size, because a
display preference does not overrule a judgement (FR-CULL-12).
**Withdrawal, which the setting does not work without.** Raising the smallest group stops the pass
*creating* small groups; it does not by itself remove the ones a previous pass made, because those
still hold their suggestions, so they are not empty, so `prune_empty_unnamed` leaves them. The pass
therefore now releases every unanchored face it did not place — `faces::unassign` — before pruning.
Without that step the control appears to do nothing until the library is reindexed.
**The preview.** `dr_ui::faces::preview_grouping` runs the same population through
`dr_face::cluster` and reports groups, faces grouped and largest group without opening a
transaction. It is `face_index --tune`'s row for one setting, on the user's own library, on a worker
thread. The line leads with the **group count** because that is the number that says which side of
the right setting you are on: it climbs as fragments are gathered into people and falls as separate
people start being welded together, while the grouped-face count rises straight through both.
---
## 10. Catalog and jobs