diff --git a/internal/cat/flex.go b/internal/cat/flex.go index a9dab3c..e549757 100644 --- a/internal/cat/flex.go +++ b/internal/cat/flex.go @@ -60,6 +60,9 @@ type Flex struct { txRawLogged bool // log the first raw transmit status once (field-name audit) spotsEnabled bool // push cluster spots + manage the panadapter overlay + // foreignSpotSeen counts what probeForeignSpot has already reported, so a + // skimmer posting all evening cannot turn the log into its own transcript. + foreignSpotSeen int spotIdx map[int]bool // panadapter spot indices currently known to the radio pendingSpot map[int]string // seq → callsign, awaiting the spot index in the R response pendingSpotMode map[int]string // seq → ADIF mode, paired with pendingSpot @@ -883,6 +886,9 @@ func (f *Flex) handleStatus(payload string) { f.spotIdx[idx] = true } f.mu.Unlock() + if !removed { + f.probeForeignSpot(payload) + } } debugLog.Printf("Flex: status %s", payload) } @@ -1377,6 +1383,40 @@ func (f *Flex) ZoomPan(bandwidthMHz, freqMHz float64, centre bool) error { return nil } +// foreignSpotProbeMax bounds the probe below. Enough lines to see what another +// program writes into a spot, few enough that a running skimmer — which posts +// hundreds an hour — cannot fill the log file with them. +const foreignSpotProbeMax = 60 + +// probeForeignSpot records, verbatim, the spots this radio receives from +// programs OTHER than OpsLog. +// +// A CW skimmer (SDC, for one) posts what it decodes as panadapter spots, the +// exchange included: chasing a split pileup means finding the "5NN" the DX just +// sent and moving the transmit slice there. Acting on that requires knowing +// exactly what the spot looks like — which field carries the text, whether the +// report is the callsign or the comment, what source names the skimmer — and no +// amount of reasoning substitutes for reading real lines off a real radio. +// +// So this logs and does nothing else. It is deliberately not a feature. +func (f *Flex) probeForeignSpot(payload string) { + if strings.Contains(payload, "source=OpsLog") { + return + } + f.mu.Lock() + if f.foreignSpotSeen >= foreignSpotProbeMax { + f.mu.Unlock() + return + } + f.foreignSpotSeen++ + n := f.foreignSpotSeen + f.mu.Unlock() + debugLog.Printf("flex: foreign spot %d/%d: %s", n, foreignSpotProbeMax, payload) + if n == foreignSpotProbeMax { + debugLog.Printf("flex: that is enough foreign spots to go on — no more will be logged this session") + } +} + // SendSpot renders a cluster spot on the panadapter via "spot add". Spots carry // a lifetime so the radio expires them on its own (the API has no "spot clear"). // Per the SmartSDR API, spaces inside a field value are encoded as 0x7F.