Skip to content

feat: estimated positions for repeaters known only from their reported name (map) - #18

Merged
efiten merged 2 commits into
masterfrom
feat/estimated-repeater-position
Oct 16, 2026
Merged

efiten merged 2 commits into
masterfrom
feat/estimated-repeater-position

Conversation

@efiten

@efiten efiten commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Summary

A repeater that told a CoreDrive companion its name (client_node_names, #17) but never sent an advert has no nodes row and no position. This PR makes it visible on the map at an estimated position, drawn so it cannot be mistaken for an advert position. Part 1 of 2: the nodes list, search and a detail view follow in a separate PR.

  • 45127c91 Server: GET /api/nodes/reported (cmd/server/reported_nodes.go) lists every repeater in client_node_names without an advert position in nodes / inactive_nodes.
    • Only direct receptions count: discover/advert rows keyed by the full pubkey or its 8-byte prefix, plus rxlog rows whose 2-3 byte hash resolves uniquely to it. geo rows are left out.
    • Receptions within 100 m form one spot, with the best SNR of that spot.
    • An estimate needs 3 spots at least 200 m apart. It is the SNR-weighted centroid (10^(snr/20)), with radius = distance to the farthest spot, minimum 500 m.
    • The response is cached for 5 minutes and the compute time is logged. Documented in openapi.go.
  • 587f4634 Map: public/map-estimated.js draws each estimate on its own layer:
    • a hollow dashed "≈" marker and a dashed uncertainty circle
    • a popup with the "Estimated position" badge, the method and the one-sided-driving caveat, and a link to the coverage map
    • a Display toggle "Estimated positions" (on by default, stored in localStorage), also hidden with the repeater role filter and in route view

Performance

  • The endpoint runs one grouped query on client_node_names, one lookupPrefixes call for the short hashes, and one IN query per 400 keys on client_receptions, which uses the idx_client_recept_heard_geo index.
  • The estimate is O(spots²) per repeater for the spread check; spots are at most one per 100 m visited.
  • The result is cached for 5 minutes and the compute time is logged. Not benchmarked: live has at most tens of such repeaters.

Test plan

  • go test ./... in cmd/server: pass. New tests cover spot merging, threshold, weighted centroid, radius floor, discover/rxlog/geo handling, an ambiguous path hash, blacklist, hidden name, advert position, and a missing table.
  • test-all.sh frontend unit suites: pass. New: tests/unit/test-map-estimated.js.
  • tests/e2e/test-map-estimated-e2e.js (new) and test-map-nodes-pagination-e2e.js against a static server with mocked API: pass.
  • XSS sink gate on public/map-estimated.js: pass.
  • Staging, in the browser, with a real repeater (c0cd, after a drive).

Not done: customizer exposure of the thresholds (100 m, 3 spots / 200 m, 500 m, 5 min TTL). They are hardcoded for now, per rule 8.

efiten and others added 2 commits October 16, 2026 12:11
…stimated position

A repeater that told a mobile companion its name (client_node_names) but
never sent an advert has no nodes row and no position, so the UI could not
show it anywhere. This endpoint lists every such repeater and adds an
estimate when the receptions allow one (cmd/server/reported_nodes.go).

- Receptions that count: discover/advert rows keyed by the full pubkey or
  its 8-byte prefix, and rxlog rows whose 2-3 byte path hash resolves
  uniquely to the repeater (lookupPrefixes). geo rows are left out.
- Receptions within 100 m form one spot with the best SNR.
- Estimate only from 3 spots at least 200 m apart: SNR-weighted centroid,
  weight 10^(snr/20), radius to the farthest spot, at least 500 m.
- A repeater with an advert position in nodes or inactive_nodes is left out;
  blacklist and hidden name prefixes apply.
- Cached 5 minutes, compute time logged. List capped at 500 repeaters.
- Documented in openapi.go.

Tests: spot merging, threshold, weighted centroid, radius and floor, the
endpoint with discover/rxlog/geo rows, an ambiguous path hash, blacklist,
hidden name, advert position, missing table. Full server suite passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Repeaters from /api/nodes/reported with an estimate get their own layer
(public/map-estimated.js), never clustered with advert markers:

- a hollow dashed marker with "≈", in the repeater colour
- a dashed, non-interactive circle with the uncertainty radius
- a popup with an "Estimated position" badge, "No advert position", the
  radius, receptions and spots, first/last heard, who reported the name,
  the one-sided-driving caveat, and a link to the coverage map there
- an "Estimated positions" toggle under Display, on by default and stored
  in localStorage; hidden too when repeaters are filtered out or a route
  is shown

The map keeps working against a server without the route (fetch failure
leaves the list empty). Method and limits in docs/client-rx-coverage.md.

Tests: tests/unit/test-map-estimated.js (vm sandbox, Leaflet stub) and
tests/e2e/test-map-estimated-e2e.js (mocked API: marker, dashed circle,
popup text, toggle and its stored state, no page errors), registered in
test-all.sh, scripts/non-unit-tests.json and the E2E workflow. All
frontend unit suites pass; the new E2E and test-map-nodes-pagination-e2e
pass against a static server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@efiten
efiten merged commit 9b504c0 into master Oct 16, 2026
12 checks passed
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.

1 participant