fix(bandopen): drop the distance ceiling

The detector refused any path over 2400 km, reasoning that past a single hop the
bearing test stops meaning anything. That was wrong, and it discarded exactly the
openings worth announcing: Nexus flagged a 6 m opening at 5477 km that OpsLog
never saw, because the spots were thrown away before any test ran.

Multi-hop Es is ordinary on 6 m — 5000 km paths are common, 10000 km happens —
and it stays directional: a second hop leaves the sector the first one entered.
So the sector test, which is what does the real work here, holds perfectly well
at any distance. The ceiling was standing in for a judgement it could not make.

The floor stays at 500 km: a short 6 m contact is tropo or ground wave and says
nothing about the ionosphere. Both are now pinned by a test.
This commit is contained in:
2026-08-11 12:30:51 +02:00
parent 29bdea1e43
commit 2d3bfa704e
3 changed files with 63 additions and 11 deletions
+43
View File
@@ -182,3 +182,46 @@ func TestBearingArcWrapsAtNorth(t *testing.T) {
t.Errorf("bearings on all four quadrants should span nearly the circle, got %d", s)
}
}
// Multi-hop must be detected, not thrown away.
//
// The detector used to cap paths at 2400 km, on the reasoning that past one hop
// the bearing test stops meaning anything. Nexus flagged a 6 m opening at
// 5477 km that OpsLog never saw: the spots were discarded before any test ran.
// Multi-hop Es is directional — a second hop leaves the sector the first one
// entered — so distance is not what tells an opening from noise. The sector is.
func TestMultiHopOpeningIsDetected(t *testing.T) {
d := New(DefaultConfig())
base := time.Date(2026, 6, 15, 18, 0, 0, 0, time.UTC)
var got *Opening
for i, call := range []string{"LU1AA", "PY2BB", "LU3CC", "PY4DD"} {
if op := d.Add(Spot{
Call: call, Band: "6m",
DistKm: 5400 + i*40, // double hop, far past the old ceiling
Bearing: +i * 5, // one sector, as a real cloud illuminates
At: base.Add(time.Duration(i) * time.Minute),
}, 47.0); op != nil {
got = op
}
}
if got == nil {
t.Fatal("a four-station 5400 km burst in one sector was not reported as an opening")
}
if got.Band != "6m" {
t.Errorf("band = %q, want 6m", got.Band)
}
}
// The floor stays: a short path says nothing about the ionosphere.
func TestGroundWaveIsStillIgnored(t *testing.T) {
d := New(DefaultConfig())
base := time.Date(2026, 6, 15, 18, 0, 0, 0, time.UTC)
for i, call := range []string{"F1AA", "F2BB", "F3CC", "F4DD"} {
if op := d.Add(Spot{
Call: call, Band: "6m", DistKm: 120, Bearing: 90,
At: base.Add(time.Duration(i) * time.Minute),
}, 47.0); op != nil {
t.Fatalf("a 120 km burst was reported as an opening: %+v", op)
}
}
}