From 104d2c4453c2ebc1a1ea8e3608c1dcea17f17be2 Mon Sep 17 00:00:00 2001 From: ruvnet Date: Tue, 28 Jul 2026 04:42:25 +0000 Subject: [PATCH] deploy: e6062977c965e9dd5603a3b1587a9da49caf9b17 --- api-docs/calibration-guide.md | 300 ++++++++++++++++++++++++++++ api-docs/trust-and-engine-errors.md | 250 +++++++++++++++++++++++ 2 files changed, 550 insertions(+) create mode 100644 api-docs/calibration-guide.md create mode 100644 api-docs/trust-and-engine-errors.md diff --git a/api-docs/calibration-guide.md b/api-docs/calibration-guide.md new file mode 100644 index 00000000..dd174cc8 --- /dev/null +++ b/api-docs/calibration-guide.md @@ -0,0 +1,300 @@ +# Calibration & Room Training Guide + +This guide explains what actually happens — and what is actually *enforced* — +when you run `wifi-densepose calibrate`, `enroll`, and `train-room`. It is +written for the person setting up a room, not for developers. + +Everything below was checked against the real Rust implementation in +`v2/crates/wifi-densepose-calibration/`, `v2/crates/wifi-densepose-signal/src/ruvsense/calibration.rs`, +and `v2/crates/wifi-densepose-cli/`, not just the design ADRs. Where the design +documents (ADR-135, ADR-151) describe something that isn't actually built yet, +this guide says so explicitly. + +## The three-step pipeline + +``` +wifi-densepose calibrate --port # Stage 1: empty-room baseline (no people) +wifi-densepose enroll --room # Stage 2+3: 8 guided anchors (~4 minutes) +wifi-densepose train-room --room # Stage 4: fit the specialist bank +wifi-densepose room-status --room # check what trained / what's stale +wifi-densepose room-watch --room # live inference +``` + +`calibrate` must run first — `enroll` refuses to start without a baseline file +(`--baseline ./baseline.bin` by default), and `train-room` refuses to start +without an enrollment file. Each step writes a file the next step reads; there +is no way to skip a step. + +--- + +## 1. Is there a minimum amount of data required? + +**Yes, and for the empty-room baseline it is a hard, enforced minimum — not a +recommendation.** + +`wifi-densepose calibrate` will not produce a baseline file with fewer than +**600 recorded frames** (the default for every PHY tier: HT20, HT40, HE20, +HE40). This is `DEFAULT_MIN_FRAMES = 600` in +`v2/crates/wifi-densepose-signal/src/ruvsense/calibration.rs:48`, and it is +checked in `CalibrationRecorder::finalize()` +(`ruvsense/calibration.rs:532-538`): if fewer than `config.min_frames` frames +were recorded, `finalize()` returns +`CalibrationError::InsufficientFrames { got, need }` and calibration fails +outright — there is no partial/degraded baseline. This is pinned by a unit +test (`finalize_requires_min_frames`, same file) so it isn't accidental +behavior. + +**Important subtlety:** the CLI's `--duration-s` flag (default 30 seconds) +and the 600-frame minimum are checked *independently*. The capture loop in +`v2/crates/wifi-densepose-cli/src/calibrate.rs:135-183` stops as soon as +**either** the duration timer expires **or** 600 frames have been recorded, +whichever comes first. If your node streams CSI slower than the assumed 20 Hz +(e.g. congested WiFi, a busier ESP32), 30 seconds may not be enough to reach +600 frames, and `calibrate` will fail with an explicit +`"insufficient frames: have X, need 600"` error rather than silently +producing a short baseline. If you hit this, raise `--duration-s` rather than +overriding `--min-frames`. + +You *can* override the 600-frame floor with `--min-frames ` (0 = use the +tier default). The code prints an explicit warning when you do: + +> `[calibrate] WARN: --min-frames=N overrides ADR-135 tier default (600 for +> ht20). This relaxes the phase-concentration guarantee; do not use in +> production.` + +(`v2/crates/wifi-densepose-cli/src/calibrate.rs:112-119`). Treat this as a +debugging escape hatch, not a supported way to shorten setup. + +The CLI also independently rejects `--duration-s` below 10 seconds +(`"Fewer frames produce unreliable phase-concentration estimates"`, +`calibrate.rs:341-348`) and prints (but does not block on) a warning above 300 +seconds. + +**Guided enrollment (`enroll`) has a much lower, per-anchor floor.** Each of +the 8 guided anchors (`empty`, `stand_still`, `sit`, `lie_down`, +`breathe_slow`, `breathe_normal`, `small_move`, `sleep_posture`) is captured +for a fixed duration baked into the code — 20 seconds for the static/motion +anchors, 30 seconds for the two breathing anchors and `sleep_posture` +(`AnchorLabel::duration_s()`, `v2/crates/wifi-densepose-calibration/src/anchor.rs:98-104`). +This is **not** a CLI flag — you cannot currently shorten or lengthen an +individual anchor capture from the command line. + +Underneath that fixed duration, the anchor is only *accepted* if it clears a +quality gate (`AnchorQualityGate`, `v2/crates/wifi-densepose-calibration/src/enrollment.rs:43-53`): + +| Threshold | Default | What it checks | +|---|---|---| +| `min_frames` | **60 frames** | Anchor is rejected if fewer than 60 frames were captured — mainly catches "the ESP32 stopped streaming" mid-capture, not a real duration requirement (60 frames is a fraction of a second of streaming at typical rates) | +| `min_presence_z` | 1.5 | For anchors that expect a person, the mean amplitude z-score must exceed this or the anchor is rejected as "no person detected" | +| `empty_max_z` | 1.0 | For the `empty` anchor, the z-score must stay under this or it's rejected as "room not empty" | +| `max_still_motion` | 0.6 (60%) | For still anchors, motion-flagged frame fraction above this is rejected as "too much motion" | +| `min_move_motion` | 0.3 (30%) | For `small_move`, motion-flagged fraction below this is rejected as "not enough motion" | + +A rejected anchor is re-prompted, up to `--attempts` times (default **2**). +If an anchor is still rejected after all attempts, `enroll` moves on without +it and logs `"moving on without '