ruview @ v1746
integrity
- size
- 61.7 MiB
- downloaded
- last checked
release notes
Automated release from CI pipeline
Changes: fix: ESP32 vitals over-count + presence flicker (#998/#996) + Observatory per-person position/motion (#1050) (#1060)
- fix(firmware): gate phantom persons + add presence hysteresis (#998, #996)
Two ESP32 edge-vitals logic bugs in edge_processing.c. Both are robustness/logic fixes — NOT validated-accuracy claims. True count/PCK vs labelled ground truth remains hardware/data-gated (COM9 ESP32-S3).
#998 — n_persons over-counted (reported 4 for one person): update_multi_person_vitals() split top-K subcarriers into top_k_count/2 groups and marked EVERY group active, so one body's multipath always read the full EDGE_MAX_PERSONS. Added two pure, host-testable helpers:
- count_distinct_persons(): per-group energy gate (EDGE_PERSON_MIN_ENERGY_RATIO) + spatial dedup (EDGE_PERSON_MIN_SC_SEP) so weak/adjacent multipath groups don't count as separate bodies. Strongest group always counts (>=1).
- person_count_debounce(): a gated count must hold EDGE_PERSON_PERSIST_FRAMES consecutive frames before it's emitted, so a single noisy frame can't promote a phantom. The active flags now mark only the strongest stable_count groups.
#996 — presence flag flickered at ~50cm despite high presence_score:
the bare score > threshold compare chattered on a noisy score
(field-observed 2.6-26.7 frame-to-frame). Replaced with a Schmitt
trigger + clear-debounce (presence_flag_update): assert above
threshold, hold in the dead band down to threshold *
EDGE_PRESENCE_HYST_RATIO, clear only after EDGE_PRESENCE_CLEAR_FRAMES
consecutive sub-low frames. presence_score itself is unchanged and
still emitted for consumer-side thresholding.
All thresholds are named, documented constants in edge_processing.h. Firmware builds clean for esp32s3 (idf.py build RC=0).
Co-Authored-By: claude-flow ruv@ruv.net
- test(firmware): host C99 tests for vitals count + presence logic (#998, #996)
test/test_vitals_count_presence.c pins the two fixes with deterministic host-buildable tests (no ESP-IDF needed). 13 cases / 22 assertions, all passing under gcc 13 -Wall -Wextra:
#998 count gate: single strong signature + multipath -> count==1; two well-separated -> 2; two strong-but-adjacent -> 1 (dedup); no signal -> 0; three well-separated -> 3. #998 debounce: transient spike rejected; sustained change accepted; flapping count stays stable. #996 presence: dithering trace -> stable flag (no flicker); brief dips held by clear-debounce; genuine departure clears within hold window; dead-band holds state.
The named tuning constants are #include'd from the real
edge_processing.h so the test and firmware can never disagree on
thresholds. make run_vitals / make host_tests added; binaries
gitignored.
Hardware-gated caveat documented in the test header: these pin the decision LOGIC; the exact energy/separation/hysteresis values that best match a real room vs labelled occupancy remain on-device tuning.
Co-Authored-By: claude-flow ruv@ruv.net
- docs: record ESP32 vitals count/presence fixes (#998, #996)
CHANGELOG [Unreleased] Fixed: root cause + fix + named constants + test
- explicit hardware/data-gated caveat for both bugs.
ADR-021 Implementation Notes: dated 2026-06 entry noting the edge-path person-count + presence-flicker fixes are boolean/count emission-logic fixes, not a validated-accuracy claim; thresholds pending on-device calibration.
Co-Authored-By: claude-flow ruv@ruv.net
- fix(sensing-server): emit real field-derived person position/motion to /ws/sensing (#1050)
The Observatory 3D figure never animated because the sensing_update WS frame carried no per-person position/motion_score/pose — only image-space keypoints. The FigurePool/PoseSystem (and demo-data.js's own contract) animate each figure from persons[i].position (room-world), .motion_score (0..100), and .pose; none were on the live stream.
Honest scope (Case 2): the pipeline has no calibrated per-person room localizer or per-person skeletal pose. New field_localize module extracts the strongest peak(s) from the real signal_field grid (subcarrier variances x motion-band power) and maps the peak cell to Observatory world coords with the exact _buildSignalField transform. motion_score is the measured motion_band_power passed through; pose is set only from a real aggregate posture estimate, else None (never a fabricated skeleton). Empty/below-threshold field -> persons: [] (no phantom); present person with no resolvable peak keeps position [0,0,0], not invented coords.
attach_field_positions runs after the tracker step at all five broadcast sites. New position/motion_score/pose fields added to both PersonDetection structs. No UI change needed — the Observatory already reads these fields.
Tests: field_localize peak/coordinate/empty/separation units + observatory_persons_field_position_tests (known-peak -> emitted position, empty-room -> no phantom, pose real-or-None, below-threshold honesty). sensing-server bin 441->451, 0 failed.
Co-Authored-By: claude-flow ruv@ruv.net
- docs(changelog): record #1050 Observatory persons position/motion fix
Co-Authored-By: claude-flow ruv@ruv.net
Docker Image:
ghcr.io/ruvnet/RuView:0c2b1c16cca3f65e707b81d6b960065312b9b904
download
curl -fL -o v1746.zip https://ratatoskr.space/pkg/ruview/v1746.zip
printf '%s %s\n' '56d262ed882832722d8cbb706578db526973a09e00858723bee2a83503ac9112' 'v1746.zip' | sha256sum -c -
$url = "https://ratatoskr.space/pkg/ruview/v1746.zip"
$out = "v1746.zip"
Invoke-WebRequest -Uri $url -OutFile $out
if ((Get-FileHash $out -Algorithm SHA256).Hash.ToLowerInvariant() -ne "56d262ed882832722d8cbb706578db526973a09e00858723bee2a83503ac9112") { throw "sha256 mismatch" }
curl -fL -o v1746.tar.gz https://ratatoskr.space/pkg/ruview/v1746.tar.gz
printf '%s %s\n' '2ef30eb3f9b0b3f7e5f771efc734101dfe026433e15489dbafa7c0c5064a89e8' 'v1746.tar.gz' | sha256sum -c -
$url = "https://ratatoskr.space/pkg/ruview/v1746.tar.gz"
$out = "v1746.tar.gz"
Invoke-WebRequest -Uri $url -OutFile $out
if ((Get-FileHash $out -Algorithm SHA256).Hash.ToLowerInvariant() -ne "2ef30eb3f9b0b3f7e5f771efc734101dfe026433e15489dbafa7c0c5064a89e8") { throw "sha256 mismatch" }
download via yggdrasil mesh
curl -fL -o v1746.zip http://14cc7d57b5e70f679b851fe5b272ce17c70632ff4beb5b35ab64bc706b2485af.pk.ygg/pkg/ruview/v1746.zip
printf '%s %s\n' '56d262ed882832722d8cbb706578db526973a09e00858723bee2a83503ac9112' 'v1746.zip' | sha256sum -c -
$url = "http://14cc7d57b5e70f679b851fe5b272ce17c70632ff4beb5b35ab64bc706b2485af.pk.ygg/pkg/ruview/v1746.zip"
$out = "v1746.zip"
Invoke-WebRequest -Uri $url -OutFile $out
if ((Get-FileHash $out -Algorithm SHA256).Hash.ToLowerInvariant() -ne "56d262ed882832722d8cbb706578db526973a09e00858723bee2a83503ac9112") { throw "sha256 mismatch" }
curl -fL -o v1746.tar.gz http://14cc7d57b5e70f679b851fe5b272ce17c70632ff4beb5b35ab64bc706b2485af.pk.ygg/pkg/ruview/v1746.tar.gz
printf '%s %s\n' '2ef30eb3f9b0b3f7e5f771efc734101dfe026433e15489dbafa7c0c5064a89e8' 'v1746.tar.gz' | sha256sum -c -
$url = "http://14cc7d57b5e70f679b851fe5b272ce17c70632ff4beb5b35ab64bc706b2485af.pk.ygg/pkg/ruview/v1746.tar.gz"
$out = "v1746.tar.gz"
Invoke-WebRequest -Uri $url -OutFile $out
if ((Get-FileHash $out -Algorithm SHA256).Hash.ToLowerInvariant() -ne "2ef30eb3f9b0b3f7e5f771efc734101dfe026433e15489dbafa7c0c5064a89e8") { throw "sha256 mismatch" }
| artifact | format | size | hashes |
|---|---|---|---|
| v1746.zip | zip | 61.7 MiB |
blake3-24 5609ed57f15df94b303a032a7c6af051fa973d673350ba10
sha256 56d262ed882832722d8cbb706578db526973a09e00858723bee2a83503ac9112
sha1 788aeae2228988fb5401bb40adfbb73303087fbe
|
| v1746.tar.gz | tar.gz | 59.9 MiB |
blake3-24 bb491bdc4424ded5b316d18b98f729756693256f0b10bbf3
sha256 2ef30eb3f9b0b3f7e5f771efc734101dfe026433e15489dbafa7c0c5064a89e8
sha1 d799080ca2a26bfaf0843ea73655664ea9bcf2a1
|
install
http_archive(
name = "ruview",
urls = ["https://ratatoskr.space/pkg/ruview/v1746.tar.gz"],
integrity = "sha256-LvMOs/mws/fl93HvxzQQHf4CZDPhVInbr6fAxQZKieg=",
strip_prefix = "ruview-v1746",
)
.url = "https://ratatoskr.space/pkg/ruview/v1746.tar.gz",
install via yggdrasil mesh
http_archive(
name = "ruview",
urls = ["http://14cc7d57b5e70f679b851fe5b272ce17c70632ff4beb5b35ab64bc706b2485af.pk.ygg/pkg/ruview/v1746.tar.gz"],
integrity = "sha256-LvMOs/mws/fl93HvxzQQHf4CZDPhVInbr6fAxQZKieg=",
strip_prefix = "ruview-v1746",
)
.url = "http://14cc7d57b5e70f679b851fe5b272ce17c70632ff4beb5b35ab64bc706b2485af.pk.ygg/pkg/ruview/v1746.tar.gz",