- **`ClusterDist` does not bound how wide a cluster may be.** DBSCAN links a face to a *neighbor*, so a line of bridge faces chains two people into one group whose extent nothing limits. `face.ClusterFits` states the width a cluster may reach and still accept its own members - `ClusterRadius + MatchDist`, because `ClampSampleRadius` stops the stored radius widening past `ClusterRadius` - and `splitWideClusters` re-clusters a group that exceeds it at a shorter link distance, cut by a flat factor rather than one sized to the overrun. A group that already fits is passed through, so a library that does not chain pays nothing; one that stays wide after `ClusterSplitRounds` is reported and **discarded** rather than created - so a gentler cut needs more rounds to reach the same separation, and the two limits have to be read together. `PHOTOPRISM_FACE_CLUSTER_SPLIT_ROUNDS` therefore has three meanings that do not order the way their numbers do: `-1` (`ClusterSplitOff`) keeps a wide group whole, `0` discards every one of them, and a positive budget splits that many times. Its flag is hidden rather than absent, so `photoprism faces status` reports what a run used. This is also the only protection an *anonymous* cluster has: `Face.ResolveCollision` returns early when a cluster has no subject, so a chain that forms, absorbs several identities, and is then named as one of them is never reported by it. Two limits: it bounds *width* rather than purity, so a short chain whose extent stays inside the accept distance still passes; and it runs where a cluster is created, so the migration and merge paths, which build centroids of their own, are not covered by it.
0 commit comments