Keep the band when the frequency is past the edge

Contest operating goes outside the band edges. Someone answers a CQ at 14352, or
a radio reports a frequency a few hundred hertz off, and Bands.ForFrequency
returned null: the contact scored nothing, missed the per-band dupe check and
exported with no band.

N1MM buckets a frequency by its whole megahertz — its BandFor divides the
kilohertz by 1000 and switches on the result, with 1 and 2 both 160M, 3 and 4
both 80M, 28 and 29 both 10M — and never looks at a band edge. Same rule here,
except that the real edges are checked first: 2190M and 630M are both under
1 MHz and share the bucket, so nothing else can tell them apart. A frequency
under 1 MHz that misses both edge ranges still has no band, as it does in N1MM.

The bandmap clamped its slice to the band edges, so a radio past the edge left
the receiver bar off the top of the scale. The slice now stretches to wherever
the radio is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 23:01:39 +00:00
parent d375d86466
commit e6d0b58305
4 changed files with 65 additions and 6 deletions

View File

@@ -94,16 +94,20 @@ public sealed partial class BandmapWindow : RefreshableWindow
return markers;
}
/// The slice to show, centred on the receiver and kept inside the band.
/// The slice to show, centred on the receiver. Contest operating goes
/// outside the band edges, so the slice stretches to wherever the receiver
/// is rather than leaving it off the top of the scale.
private (Frequency Low, Frequency High) Window(Band band, Frequency centre)
{
long lowEdge = Math.Min(band.Low.Hertz, centre.Hertz);
long highEdge = Math.Max(band.High.Hertz, centre.Hertz);
Frequency span = Spans[Math.Max(0, SpanBox.SelectedIndex)].Span;
if (span.Hertz == 0 || span.Hertz >= band.High.Hertz - band.Low.Hertz)
if (span.Hertz == 0 || span.Hertz >= highEdge - lowEdge)
{
return (band.Low, band.High);
return (Frequency.FromHertz(lowEdge), Frequency.FromHertz(highEdge));
}
long half = span.Hertz / 2;
long low = Math.Clamp(centre.Hertz - half, band.Low.Hertz, band.High.Hertz - span.Hertz);
long low = Math.Clamp(centre.Hertz - half, lowEdge, highEdge - span.Hertz);
return (Frequency.FromHertz(low), Frequency.FromHertz(low + span.Hertz));
}

View File

@@ -51,7 +51,15 @@ public static class Bands
private static Band Make(string name, double label, double lowKhz, double highKhz) =>
new(name, label, Frequency.FromKilohertz(lowKhz), Frequency.FromKilohertz(highKhz));
/// Null when the frequency is outside every amateur allocation.
/// The band a frequency counts as, or null when no band covers that
/// megahertz.
///
/// Contest operating goes outside the band edges: an operator answering a
/// CQ near the edge, or a radio reporting a frequency a few hundred hertz
/// off. N1MM buckets a frequency by its whole megahertz rather than by the
/// edges, so 14400 kHz is still 20M and the contact keeps its band. We do
/// the same. Exact edges are checked first, because 2190M and 630M are both
/// under 1 MHz and the bucket cannot tell them apart.
public static Band? ForFrequency(Frequency f)
{
foreach (Band band in All)
@@ -61,6 +69,18 @@ public static class Bands
return band;
}
}
long megahertz = f.Hertz / 1_000_000;
if (megahertz == 0)
{
return null;
}
foreach (Band band in All)
{
if (megahertz >= band.Low.Hertz / 1_000_000 && megahertz <= band.High.Hertz / 1_000_000)
{
return band;
}
}
return null;
}