Face indexing was compiled into the APK all along — dr-ui takes dr-face with `inference` on every target, so SCRFD, alignment, MBF, calibration and clustering were all in there. What was missing was the weights, and on Android there was no way to supply them. Route C (docs/faces.md §2.2) says the user obtains the model and the app loads it. On a desktop that is a real gesture: drop two files in ~/.local/share/darkroom/models/ and indexing starts working. On Android it is not a gesture at all. `internal_data_path` is app-private, `run-as` needs a debuggable build, and the in-app fetch route C specifies was never built — so the settings page reported "no face model is installed" on every launch with nothing behind the message. Not "off until you supply weights"; off. So the shape-fixed pair goes into LFS under the APK's assets, assemble-apk.sh copies it into the package, and `android_main` unpacks it to the shared models directory before anything asks whether a model is present. Three things that are not incidental: The models directory is now shared across accounts rather than per-account. Weights are identified by `faces.model_id`, not by who is signed in, so two accounts had no reason to hold two copies — and the unpack runs before any session exists to key a per-account path off. `face_models` still prefers a per-account directory when one is populated, so anyone mid-migration keeps the ability to pin one library to its own pair. The unpack writes under a temporary name and renames. `face_models` decides availability on `is_file()` alone, so a copy truncated by the process being killed would leave a file that passes that test and fails inside tract — reported to the user as a broken model rather than a missing one. assemble-apk.sh refuses an LFS pointer. At ~130 bytes it looks exactly like a model to `cp`, and unchecked it reaches the device and fails in the graph loader instead of telling someone to run `git lfs pull` — the same guard dr-segment's build script applies to yolo26n-seg.onnx. The licensing half is unchanged and recorded in §2.2a: the InsightFace grant is research-only, this is a private repository and a self-installed build, and these files come back out before anything is published. The weights are still not a cargo build input — dr-face has no `models/` directory and no `embedded-model` feature, and nothing in the build reads them. The APK assembly step copies two files and is the only thing in the tree that knows they exist. Verified on device: both models unpack on first launch (2524817 and 13616095 bytes) and the APK carries them at assets/models/. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
APK assets
models/ holds the two face models, in LFS. docker/android/assemble-apk.sh copies them into the
APK and android_main unpacks them to the shared models directory on first launch, which is the only
way a phone can be handed a model at all — app-private storage has no user-reachable route into it,
and the in-app fetch docs/faces.md §2.2 specifies is unbuilt.
models/scrfd_500m_640.onnx 2.5 MB
models/arcface_mbf_b1.onnx 13 MB
A clone without git-lfs gets a ~130-byte pointer file where each model should be. assemble-apk.sh
checks for exactly that and refuses, rather than bundling the pointer and failing inside tract on the
device — the same guard dr-segment's build script applies to yolo26n-seg.onnx. Fix it with
git lfs pull.
These are not what InsightFace ships. They came from buffalo_sc.zip and buffalo_s.zip on the
InsightFace v0.7 release with their input dimensions pinned, because tract cannot parse either graph
while they are dynamic:
./tools/fix-face-model-shapes.sh det_500m.onnx models/scrfd_500m_640.onnx --input input.1=1,3,640,640
./tools/fix-face-model-shapes.sh w600k_mbf.onnx models/arcface_mbf_b1.onnx --dim None=1
The weights carry a non-commercial research-only grant. They are here because this is a private repository and a self-installed build; they come back out before anything is published, and the restriction binds whoever uses the app, not only the project. docs/faces.md §2.2a is the decision.