The engine knew f32 and int8, and gave the Hexagon int8 for every role it served. Measured on the tablet itself (inference.md §1.5), int8 lost 5% of the detector's faces at 40-80 px, moved the landmarks 1.5 px, emptied the segmenter's scores and cost the denoiser 5-9 dB; fp16 the HTP refuses outright. `Form` gains A16W8 and A16W16, and `Rung::form` now names one per role: detectors and landmarks A16W8, the segmenter, scene model, border filler and denoiser A16W16, XFeat int8. The embedder and the eye classifiers stay on the CPU. Each loader resolves its `<stem>.<form>.onnx` sibling; the segmenter and XFeat, compiled into the binary, embed their quantised forms on Android only and pick through `choose_embedded`. The probe, the compile step and the cache fingerprint follow the form instead of assuming int8. Detectors on the new form write `scrfd_*_a16+w600k_mbf`, and `model_ids` answers for all three spellings. On the tablet (ORT 1.29 + QNN 2.42), each shipped file against f32 on the same inputs, and against the CPU's f32 time: SCRFD 500m/2.5g/10g A16W8 100% of faces in every band 4.2/5.1/9.0 ms vs 17/56/198 landmarks A16W8 0.25 px in the 192 crop 0.5 ms vs 2.8 YOLO26n-seg A16W16 98.2% found, mask IoU 0.994 12.9 ms vs 90 scene model A16W16 98.9% of cells agree 15 ms vs 151 MI-GAN A16W16 41 dB from f32 in the fill 87 ms vs 488 XFeat int8 pano alignment 0.45 px (f32's own spread 0.41) 6.5 ms vs 58 denoiser A16W16 0.00 dB at every ISO 95 ms vs 1510 a tile Face numbers are over public COCO val2017 photographs, not a library. The APK carries the siblings (BUNDLED 15 -> 19; the old int8 detectors removed), about 43 MB more. The Windows installer and its CI count skip them; the Arch and Flatpak packages list their files and never had them. The ladder example takes a role per model, which is how the per-role forms above were seen landing on the NPU from the real probe.
Documentation
Two audiences, two folders. Most people want the first table and never the second.
Using DarkRoom
| The manual | Every feature, pictured from the application itself — opening a library, rating, labelling and filing, duplicate originals, developing, local masks, repair, film, presets, panoramas, export and albums. The application carries it and opens it from Help and from Settings |
| How it is driven | Every gesture and shortcut, by screen. Generated from the code, so it cannot describe one the application does not have. The same list is the in-app help sheet: Help or F1 in the grid, ? or F1 in develop |
The top-level README says what DarkRoom is, how to get it on each platform, and what is still missing.
Changing DarkRoom
Everything under dev/ is for someone working on the code. Start with
CONTRIBUTING.md, which says how to land a first change
without reading the rest.
The register and the record. What must be built, how it is built, and how far along it is.
| requirements.md | What the software must do — the numbered register, the decisions (D-numbers) and the spikes (S-numbers) |
| architecture.md | How it is built — crates, the GPU pipeline, the data model, sync |
| traceability.md | Generated: which requirement is claimed by which file. Never edited by hand |
| outstanding.md | What is specified and not built, and whether that is a decision or a gap |
| technical-debt.md | Compromises taken deliberately, each with the condition that retires it |
| code-health.md | What a contribution costs, per seam, measured |
Designs, one per subsystem. Each is the specification the code was built to, kept current as the code moved.
| catalog.md | The index, the library view, incremental scan, the job queue |
| storage.md | Storage backends: the seam a folder, a sync client and a Nextcloud account share |
| faces.md | Face detection, identity, clustering and the eye-state models |
| segmentation.md | How the application finds the regions a local mask snaps to |
| mask-editing.md | Painting, erasing and combining masks |
| spot-removal.md | Clone and heal as parameters in the edit graph |
| panorama.md | Alignment, projection, the chunked composite and the border fill |
| inference.md | The neural runtime and model chosen per device, with the measurements |
| display-and-extension.md | The display contract, and why the fused pipeline is already most of a plugin format |
| view-composition.md | A controller for the display layer |
| ui-navigation.md | Finding things in the interface once there are many |
Measurements. Numbers committed so a regression is a diff rather than a recollection.
| benchmarks.md | The per-commit suite: what it covers, what it does not, how to read a failure |
| bench-baseline.json | The committed numbers the suite checks against |
| frame-budget.md | What a frame costs on each device, and the decision those figures settled |
Platforms and distribution.
| distribution.md | Which channels v1 targets and what each one constrains |
| windows.md | The Windows installer, cross-built from the Linux CI |
| android-signing.md | Which key signs the APK, and keeping it |
Archive. Kept as the record of what was asked for, not as plans.
| milestone-v0.1.md | The first milestone, delivered 2026-08-30 and superseded |
| ui-refinement.md | How the interface should look; succeeded by ui-navigation.md |
Conventions
Three files here are generated and must not be edited by hand:
gestures.md, dev/traceability.md and manual/index.html, the page the
packages install, rendered from manual/README.md. All three come from
cargo run -p traceability, the pre-commit hook keeps them in step with the
tree, and CI fails when one is not what the tree generates. The manual's
pictures are recorded by tools/manual and live
in LFS.
A design document links to the requirements it satisfies and to the code
that satisfies them. When the code moves, the link moves with it; a document
that has stopped being true goes to dev/archive/ with a note saying what
replaced it, rather than being deleted.