fix(autocall): the QSO in progress outranks the ladder

Six faults from an evening on 60 m, all in the same family: the engine
judging a station by what the log wants from it and forgetting what is
already under way.

- An exchange was abandoned mid-QSO. The reply lands in the same period
  the ladder is re-read, and that period was judged before the reply was
  taken into account, so a better-ranked caller took the slot from a
  station that had just come back to us. The answer is settled first now,
  and our own report counts as being inside the exchange too — which also
  protects a QSO the operator started by hand.
- A station just picked started with misses against it. Its transmit slot
  was unknown until a second decode, and with the parity unknown every
  period counted, including the one spent transmitting to it.
- The freed slot after "it is working somebody else" was thrown away: the
  period's decodes are in hand, so the next station is picked from them
  rather than fifteen seconds later. Never mid-over.
- Auto-call is never armed from a stored setting — not at launch, not on
  a profile switch. It is the one feature that puts the station on the
  air by itself and OpsLog starts with Windows.
- It says what it is waiting for: a wanted station in a QSO with somebody
  else now shows beside the Auto button instead of looking idle.
- Switching profile left the previous logbook's verdicts on screen. The
  worked-index, chase-new and the frontend's cached verdicts are dropped
  when the logbook changes.

FT decodes: distance column, a message addressed to you set whole in
green (the station you are calling keeps a tint — most of what it sends
goes to other people), badge order L / Wkd / WL, list cleared when the
RIG changes band.

Rotor: new world-map compass from EC1KD's design, with the Ultrabeam boom
and second lobe restored and the compact form preserved; the classic dial
is kept and Settings → Rotator chooses between them. Stop no longer
flickers on a rotor standing still — movement was inferred from a degree,
less than the jitter a controller reports at rest.
This commit is contained in:
2026-09-05 23:02:31 +02:00
parent 8dbc4b7e62
commit 23323c91e0
14 changed files with 1752 additions and 389 deletions
+28 -8
View File
@@ -195,8 +195,10 @@ type AutoCallStatus struct {
// Only is the chase list, carried in the status so the field in the decodes
// toolbar and the one in Preferences are never two versions of the truth:
// whichever is typed into, both show it.
Only string `json:"only"`
Target string `json:"target"`
Only string `json:"only"`
Target string `json:"target"`
// Waiting: what it would call if that station were not in a QSO.
Waiting string `json:"waiting"`
Calls int `json:"calls"`
Max int `json:"max"`
Misses int `json:"misses"`
@@ -218,7 +220,7 @@ func (a *App) GetAutoCallStatus() AutoCallStatus {
set := a.GetAutoCallSettings()
return AutoCallStatus{
Enabled: set.Enabled, Only: set.Only,
Target: st.Target, Calls: st.Attempts, Max: st.Max,
Target: st.Target, Waiting: st.Waiting, Calls: st.Attempts, Max: st.Max,
Misses: st.Misses, MaxMiss: st.MaxMiss, Stopped: st.Stopped,
Greylisted: a.autoCallEngine().Greylisted(),
Reason: reason,
@@ -588,16 +590,34 @@ func (a *App) autoCallWatched(call string) bool {
return ok
}
// startAutoCall arms the engine at launch.
// startAutoCall starts the engine's loop, with auto-call OFF.
//
// The stored "on" is honoured, and the engine starts with NO target: a program
// that came up already calling a station chosen before the last shutdown is not
// something an operator can be expected to anticipate.
// It is never on from a stored value. This is the one setting in the program
// that puts the station on the air by itself, and the operator who left it on
// last night is not necessarily the one at the desk now — nor necessarily at
// the desk at all: OpsLog starts with Windows, and a rig that powers up with it
// would begin calling into an empty shack, on whatever band the radio happens
// to be on, hours after anybody decided that was a good idea.
//
// Arming it is one click, and it is a click somebody has to make.
func (a *App) startAutoCall() {
a.applyAutoCall()
a.disarmAutoCall("launch")
go a.autoCallLoop()
}
// disarmAutoCall switches auto-call off and writes that down.
//
// The setting is what the toolbar and the settings panel both read, so turning
// the engine off without storing it would show a lit switch over a silent
// transmitter — and the operator's next click, meaning "on", would send "off".
func (a *App) disarmAutoCall(why string) {
if a.settingOr(keyAutoCallOn, "0") == "1" {
applog.Printf("autocall: off at %s — it is never armed from a stored setting", why)
}
a.setSetting(keyAutoCallOn, "0")
a.applyAutoCall()
}
func (a *App) autoCallLoop() {
// A quarter of a second. The sweeper is what closes a period, so its tick is
// part of the same budget as acQuiet: a two-second tick added up to two