Skip to content

fix(edge): gate multi-person count on presence — no phantom persons (#803, #496)#892

Open
markt-heximal wants to merge 1 commit into
ruvnet:mainfrom
markt-heximal:fix/edge-phantom-persons
Open

fix(edge): gate multi-person count on presence — no phantom persons (#803, #496)#892
markt-heximal wants to merge 1 commit into
ruvnet:mainfrom
markt-heximal:fix/edge-phantom-persons

Conversation

@markt-heximal
Copy link
Copy Markdown

Addresses #803 and #496.

Problem

update_multi_person_vitals() (edge_processing.c) sets pv->active = true unconditionally for n_persons = s_top_k_count / 2 (= 4 at the default EDGE_TOP_K=8) every frame. So the reported n_persons is a fixed function of the subcarrier count — never 0, regardless of how many people are actually present. An empty room reports 4; users see a constant phantom count.

Root cause behind:

The firmware already computes presence (s_presence_detected, set earlier in the same process_frame() pass) — but the multi-person path never consults it.

Fix

Gate person activation on s_presence_detected: when no presence is detected, mark all persons inactive and return → n_persons = 0 in an empty room; persons activate only once presence crosses the (adaptive) threshold.

if (!s_presence_detected) {
    for (uint8_t p = 0; p < EDGE_MAX_PERSONS; p++) s_persons[p].active = false;
    return;
}

Scope / honest limitations

This bounds the count to presence (0 when empty, ≥1 when someone is there). It does not attempt true multi-person separation — when present, the count is still the subcarrier-derived top_k/2. Real counting/separation from a single link is a larger DSP problem; this PR just removes the "always N phantom people" behavior everyone is hitting.

Testing

  • Builds clean — ESP-IDF v5.4, idf.py set-target esp32s3 && idf.py build.
  • The gating signal was verified on hardware: presence_score (= motion_energy) / s_presence_detected read ~0 at idle and rise with movement, so the gate yields 0 when empty and activates on presence. The pre-fix constant count (4 at top_k=8) was observed directly on an ESP32-S3.

🤖 Generated with Claude Code

…phantom persons

update_multi_person_vitals() marked top_k/2 persons active unconditionally every
frame, so the reported n_persons was a fixed artifact of the subcarrier count
(never 0) regardless of occupancy — an empty room reported e.g. 4 people. The
firmware already computes s_presence_detected (earlier in the same process_frame
pass, currently used only for a packet flag); gate person activation on it so an
empty room reports 0 persons. Addresses ruvnet#803 and ruvnet#496.

Scope: bounds the count to presence; true multi-person separation is out of scope.
@ruvnet
Copy link
Copy Markdown
Owner

ruvnet commented Jun 2, 2026

Independently verified — this fix is correct, and the call ordering it relies on checks out against current main (edge_processing.c):

  • process_frame() runs update_top_k() (L745) → sets s_presence_detected = (s_presence_score > threshold) (L817) → calls update_multi_person_vitals() (L851).
  • This PR's gate sits at the top of update_multi_person_vitals (L478), i.e. it executes at L851 — after L817 — so s_presence_detected is the freshly-updated value for this frame. The comment's "updated earlier in the same process_frame() pass" claim is accurate.

Before this, update_multi_person_vitals activated s_top_k_count / 2 persons whenever s_top_k_count >= 2 (essentially always), so an empty room reported a fixed top_k/2 phantom count (the #803/#496/#894 "4–10 people when nobody's there"). Gating activation on presence is the right minimal fix; the existing for (p = n_persons; p < EDGE_MAX_PERSONS) active = false tail already handles the partial case, and this extends it to the zero-presence case.

Worth noting this is the edge/firmware half of the over-count. The server heuristic path has a complementary cap on mainfield_bridge.rs bounds occupancy to MAX_SINGLE_LINK_OCCUPANCY = 3 (#907). Together they close both layers: the ESP32 no longer emits phantom persons, and the server can't amplify a single link past 3.

LGTM to merge. (Happy to run an ESP-IDF build to confirm it compiles if that's useful before merge — it only touches existing symbols, so it should be clean.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants