Received message path: Sender → hop1 (near sender) → ... → Us
Outbound path: Us → hop1 (near us) → ... → Contact
Was using outbound path which placed contacts near OUR repeater.
Now uses first hop of the received message path — the repeater
nearest to the sender. Only triggers on received messages, not sends.
When a contact has no GPS, estimate their position from RSSI
observations through multiple repeaters:
- 1 repeater: offset by RSSI distance in deterministic direction
- 2 repeaters: weighted midpoint between circle intersections
- 3+ repeaters: weighted centroid with inverse-square weighting
(closer repeater observations dominate)
Observations stored per-repeater (latest wins), max 8 per contact,
expire after 30 minutes. Each incoming message updates the estimate.
For contacts without GPS, estimate their position by:
1. Finding the first-hop repeater from their routing path
2. Estimating distance from RSSI using log-distance path loss model
3. Placing them at a deterministic offset from the repeater
Uses LoRa outdoor path loss exponent (n=3.0) with reference RSSI
-30dBm at 1m. Distance clamped to 10m-50km range. Bearing derived
from contact public key hash for stable positioning.
- pushAdvert (0x80): import advert into firmware via cmdGetAdvertPath
before fetching contact, so name/type/location are available
- pushNewAdvert (0x8A): fire-and-forget importReceivedAdvert to store
contact in firmware (data already in push payload)
- controlData (0x8E) discovery: no firmware import (matches official app),
just lookup existing contacts and show generic labels for unknown nodes
- Update meshcore_client git ref to bcbeff0 (burst sync + advert import)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- No automatic IR1 requests; fragments arrive only when user taps load
- Bubble sends IR1 with missing indices only if partial session exists
(partial resume: only missing fragments requested, not the whole image)
- 30s timeout resets the loading spinner so user can tap retry again
- Saves LoRa airtime by never transmitting IR1 without explicit user action
- Replace 1.2s fixed retry with 10s idle debounce timer
- Timer resets on every received fragment (debounce)
- Timer also starts on IE1 envelope receipt (handles pull-only senders)
- IR1 always requests only missing indices (partial resume)
- Attempt counter no longer resets per fragment; bounded to 5 retries
- Max attempts raised to 5 (was 4)