Skip to content

packets: an ambiguous path hop is shown as a single certain name, resolved without the geographic filter #2097

Description

@efiten

The packets page shows one name for an ambiguous path hop, with no indication that it is a guess, and it picks that name without using the geographic filtering the resolver already implements. In the case below the displayed repeater is 185 km from the observer, while the evidence in the database points at a different candidate.

hop-resolver.js computes the ambiguity correctly. packets.js throws it away.

What a user sees

A path hop renders as a single repeater name, identical in weight to an unambiguous one. Nothing says "1 of 9". An operator reading a path has no way to tell a certain hop from a coin flip, and acts on the wrong one.

Worked case from live data

A GRP_TXT flood observed on 2026-09-30, path ["EF","E7","EE","05","7E","AE","44","38"], observed by BE-BGS-RRY120-OBS (IATA OST).

Nine nodes share the prefix EF:

node role distance from the transmitter distance from OST
BE-9041-REP-VER repeater 0.0 km ~62 km
💀RRY6710yagiPnshr repeater 10.6 km ~55 km
BE-BLZ-WALTWILDER-EDG repeater 126.0 km ~185 km
BE-BRE-ON8AR🔋 repeater 126.5 km ~185 km
DE-NW-D-R01 repeater 210.4 km
Dui_Rahm repeater 211.9 km
D-WAN-Eickel (Test) repeater 240.0 km
LDN-SM4 📡 / sapphire repeater 280.1 km
🐧 companion no GPS

The UI shows BE-BLZ-WALTWILDER-EDG.

The evidence available at that moment points elsewhere. The next hop in the path is E7, and of the nine EF candidates exactly one has a recorded neighbour with prefix E7:

BE-9041-REP-VER          neighbours=7    with prefix E7: 1   (BE-KOU-ON6BW-Repeater, 104 observations)
BE-BLZ-WALTWILDER-EDG    neighbours=33   with prefix E7: 0
💀RRY67~10yagi~Pnshr     neighbours=10   with prefix E7: 0

So graph affinity, geography and the transmitter's own position all agree on BE-9041-REP-VER, and the page shows the one candidate none of them support.

Root cause

public/hop-resolver.js exposes:

function resolve(hops, originLat, originLon, observerLat, observerLon, observerId)

public/packets.js:966-975 calls it with one argument out of six:

async function resolveHops(hops) {
  const unknown = hops.filter(h => !(h in hopNameCache));
  if (unknown.length) {
    await ensureHopResolver();
    const resolved = HopResolver.resolve(unknown);
    Object.assign(hopNameCache, resolved || {});

Two consequences:

1. The geographic filter never runs. observerId is undefined, so packetIata is null (hop-resolver.js:189), so nodeInRegion() is never called and regional stays empty. The REGION_RADIUS_KM = 300 filter that would have removed the German and UK candidates is dead code on this path, and the candidate list is used in index order.

2. The ambiguity is discarded. resolve() returns ambiguous: true and a populated conflicts array for a multi-candidate hop (hop-resolver.js:217-227). Object.assign(hopNameCache, resolved) keeps only what the render reads, and nothing in packets.js references conflicts or ambiguous:

$ grep -rn '\.conflicts\|\.ambiguous' public/*.js | grep -v hop-resolver.js
public/analytics.js:461, 463, 472, 2750, 3310, 3311, 3339, 3373, 3374, 3383

Analytics alone. It renders a warning icon per ambiguous edge, counts "Ambiguous Prefixes" as a stat, and offers a confidence filter that can hide ambiguous edges. The pattern this issue asks for already exists in the codebase, one page over.

3. The cache is keyed by prefix alone, so the first packet to resolve EF fixes that name for every later packet regardless of observer or region. packets.js:1001 already carries the comment // Use per-packet cache key if observer context available (ambiguous hops differ by region), so this was known.

On whether it is fixable

One byte gives 256 values for 2043 nodes, so collisions are not going away: EF alone has nine. Perfect resolution is not the goal and is not possible.

What is possible, in increasing order of effort:

  1. Stop presenting a guess as a fact. Mark an ambiguous hop and show the candidate count. The data is already returned and already discarded. This alone would have prevented the wrong conclusion in the case above.
  2. Pass the arguments that exist. resolveHops has the observer on the packet (observer_id, observer_iata) and the transmitter's position where known. Passing them switches on the geographic filter that is already written.
  3. Key the cache per observer/region rather than per prefix, as the existing comment proposes.
  4. Use the next resolved hop. pickByAffinity already scores on graph edges and would have chosen correctly here; it needs neighbouring context the current call does not supply.

Steps 1 and 2 are small and independent, and either would have produced a defensible answer in this case.

Related

The DRY point raised on #2089 applies here too: this is another site resolving a path hash prefix with its own rules. Whatever is done here should not become a sixth variant.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions