feat(web-viewer): add flood-packet distance to the hops chart

Keeps the advert series — nodes by their closest observed path — and
adds arriving flood packets by how far they had already travelled.

The two answer different questions and, on the live mesh, disagree
usefully: nodes peak at 2-3 hops and fall away quickly, while flood
traffic peaks at 5 and holds a long tail past 16. A close-in
neighbourhood absorbing flood from well beyond it.

One series counts nodes (2.8k) and the other packets (156k), so raw
counts on a shared axis would flatten the node series into the baseline.
Both are drawn as a share of their own total, with absolute counts in
the tooltip, and padded onto one contiguous hop range so the bars line
up. They also cover different spans — 7 days of adverts against
whatever packet_stream retains — so each is labelled with its own
window instead of being presented as one period.

Watch the units. observed_paths.path_length is a BYTE count, so hops are
path_length / bytes_per_hop. packet_stream.path_len is already a HOP
count, with the byte length carried separately as path_byte_length. A
17-hop 3-byte path is path_length 51 in one table and path_len 17 in the
other. Applying either rule to the other silently rescales the axis and
the only symptom is a chart that looks a bit off, so both conventions
are now pinned by tests against the shapes real rows take.

Flood packets carry no sender identity and observed_paths holds only
adverts, so the flood series cannot be reduced to a shortest path per
node the way the advert series is. It is a per-packet distribution, and
the tooltip says so.
This commit is contained in:
agessaman
2026-07-29 22:45:21 -07:00
parent 0296c2e93e
commit 3a4d7aab33
6 changed files with 251 additions and 53 deletions
+15 -4
View File
@@ -148,10 +148,21 @@ proxy_set_header X-Forwarded-Proto $scheme;
Two measurement notes for the mesh charts:
**Hops are derived, not read.** `observed_paths.path_length` is a *byte* count,
and with 2- or 3-byte hop encoding a three-hop path is six or nine bytes long.
The dashboard divides by `bytes_per_hop`; charting the raw value would overstate
distance two- to threefold on a mesh that is ~95% multibyte.
**Hops are derived, and the two path tables disagree on units.**
`observed_paths.path_length` is a *byte* count, so hops are
`path_length / bytes_per_hop` — with 2- or 3-byte encoding a three-hop path is
six or nine bytes long, and charting the raw value would overstate distance two-
to threefold on a mesh that is ~95% multibyte. `packet_stream.path_len`, by
contrast, is *already a hop count*, with its byte length carried separately as
`path_byte_length`. Applying either table's rule to the other silently rescales
the axis, so both conventions are pinned by tests.
The hops chart shows both distributions: nodes by their closest advert path (7
days) and arriving flood packets by distance travelled (whatever
`packet_stream` retains, typically 3 days). One counts nodes and the other
packets, so each is drawn as a share of its own total. Flood packets carry no
sender identity, which is why they cannot be reduced to a shortest path per
node the way adverts can.
**Neighbour signal is reported only where two sources agree.**
`complete_contact_tracking.hop_count` is not a reliable direct-neighbour marker: