Fixes#1638.
## Problem
`getConfidenceIndicator` in `public/nodes.js` treats every observation
as equal evidence, so a node seen 5 times via 1-byte hash prefixes
(which collide ~8-way across a typical mesh) scores the same as a node
seen 5 times via 6-byte prefixes (effectively unambiguous). The user
asked for confidence to respect ambiguity.
## Change
- `cmd/server/neighbor_graph.go` — new `CountsByMode map[int]int` on
`NeighborEdge`, bumped in `upsertEdge` / `upsertEdgeWithCandidates`
based on the observation's hash-prefix byte length (1/2/4/6). Merged in
`resolveEdge` when ambiguous→resolved edges collapse.
- `cmd/server/neighbor_api.go` — `NeighborEntry.counts_by_mode` exposed
(omitempty), and `dedupPrefixEntries` merges per-mode counts when an
unresolved prefix entry collapses into a resolved one. Flat `Count`
field preserved for back-compat.
- `public/nodes.js::getConfidenceIndicator` — weights observations by
mode: 1-byte=0.125, 2-byte=0.5, 4/6-byte=1.0. A single 6-byte sighting
counts ~8× a raw 1-byte one. HIGH triggers when EITHER the legacy
heuristic clears OR weighted count ≥3. Legacy entries without
`counts_by_mode` keep working (default weight 0.5).
- Tooltip now shows the per-mode breakdown (e.g. "Observations: 5
(1-byte: 3, 6-byte: 2)").
## TDD
- RED:
`cmd/server/neighbor_graph_test.go::TestBuildNeighborGraph_CountsByMode`
— fixture with 1/2/4-byte sightings asserts per-mode tally (commit
`838965f3`).
- RED: `test-confidence-indicator.js` — 6-byte mostly-sighted neighbor
must outrank 1-byte mostly-sighted neighbor at equal flat count (commit
`4bd5e18e`).
- GREEN: implementation in commit `7511606d`. All 4 JS tests pass; new
Go test passes; full Go suite passes (two pre-existing flakes unrelated,
both pass when isolated).
## Browser verification
Synthetic side-by-side of OLD vs NEW classifier against representative
inputs — see screenshot. 1-byte-only and 6-byte-only at the same flat
count diverge from MEDIUM/MEDIUM to MEDIUM/HIGH, and 3 6-byte sightings
now upgrade where 20 1-byte sightings stay MEDIUM.
## Preflight overrides
- check-branch-scope: cross-stack: justified — backend exposes the new
`counts_by_mode` field and the frontend consumes it; the whole point of
the change.
## Compat
- `Count` field unchanged in shape and value.
- `counts_by_mode` is `omitempty`; legacy persisted edges (loaded from
`neighbor_edges` via `neighbor_persist.go`) get no per-mode breakdown
and fall back to the default weight (0.5) — no UI regression.
---------
Co-authored-by: bot <bot@local>
Co-authored-by: corescope-bot <bot@corescope.local>