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:
@@ -795,3 +795,125 @@ func TestTheSlotAfterAQSOBelongsToOur73(t *testing.T) {
|
||||
t.Errorf("next period: %+v, want the new band called", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestABetterStationTakesOverBeforeAnybodyHasAnswered(t *testing.T) {
|
||||
e := on()
|
||||
e.OnPeriod(period(0, cq("PLAIN", NeedSlot, +10)))
|
||||
// A watched station comes on the air: nothing has answered us yet, so the
|
||||
// call in progress is worth less than the one now possible.
|
||||
a := e.OnPeriod(period(2, cq("PLAIN", NeedSlot, +10), cq("WATCHED", NeedNone, -15, watched)))
|
||||
if a.Kind != DoReply || a.Decode.Call != "WATCHED" {
|
||||
t.Fatalf("%+v — a better station did not take over", a)
|
||||
}
|
||||
|
||||
// Equal rungs do NOT take over while the station being called is on the air.
|
||||
e = on()
|
||||
e.OnPeriod(period(4, cq("A", NeedBand, 0)))
|
||||
if a := e.OnPeriod(period(6, cq("A", NeedBand, 0), cq("B", NeedBand, +20))); a.Kind != DoNothing {
|
||||
t.Errorf("%+v — swapped between two stations of equal value", a)
|
||||
}
|
||||
// But they do when it is absent: seven calls into the void while a station
|
||||
// of the same value is calling CQ is what the operator was watching.
|
||||
if a := e.OnPeriod(period(8, cq("B", NeedBand, +20))); a.Kind != DoReply || a.Decode.Call != "B" {
|
||||
t.Errorf("%+v — kept calling a station that was not on the air", a)
|
||||
}
|
||||
|
||||
// Once the station has answered, nothing takes its place.
|
||||
e = on()
|
||||
e.OnPeriod(period(10, cq("DX", NeedSlot, 0)))
|
||||
e.OnPeriod(period(12, callsMe("DX", NeedSlot, 0)))
|
||||
if a := e.OnPeriod(period(14, callsMe("DX", NeedSlot, 0), cq("RARE", NeedDXCC, 0, watched))); a.Kind != DoNothing {
|
||||
t.Errorf("%+v — abandoned an exchange in progress", a)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAnExchangeInProgressIsNeverAbandoned is the shack report: mid-QSO with
|
||||
// V31MA — its report decoded, our RR73 going out — and a station of a higher
|
||||
// rung called us from the other side of the screen. The engine switched.
|
||||
func TestAnExchangeInProgressIsNeverAbandoned(t *testing.T) {
|
||||
e := on()
|
||||
// V31MA is worth nothing on the ladder: worked before, nothing needed.
|
||||
if a := e.OnPeriod(period(0, callsMe("V31MA", NeedNone, -12))); a.Kind != DoReply {
|
||||
t.Fatalf("%+v — a station calling us was not answered", a)
|
||||
}
|
||||
// Its report arrives in the same period a better station calls us. That
|
||||
// period used to be judged before the reply was taken into account.
|
||||
a := e.OnPeriod(period(2,
|
||||
callsMe("V31MA", NeedNone, -12),
|
||||
callsMe("F5NNN", NeedSlot, -10)))
|
||||
if a.Kind != DoNothing {
|
||||
t.Fatalf("%+v — left V31MA mid-exchange", a)
|
||||
}
|
||||
if e.target == nil || e.target.Call != "V31MA" {
|
||||
t.Fatalf("target is %v — the QSO in progress lost its place", e.target)
|
||||
}
|
||||
|
||||
// Our own transmission settles it too: a report is not an opening call, so
|
||||
// even a QSO the operator started by hand is protected.
|
||||
e = on()
|
||||
e.OnPeriod(period(4, cq("V31MA", NeedNone, -12, watched)))
|
||||
e.NoteTX(TXState{Transmitting: true, Msg: "V31MA F4BPO RR73"})
|
||||
if !e.answered {
|
||||
t.Error("sending a report did not count as being inside the exchange")
|
||||
}
|
||||
if a := e.OnPeriod(period(6, cq("V31MA", NeedNone, -12, watched), callsMe("F5NNN", NeedDXCC, -10))); a.Kind != DoNothing {
|
||||
t.Errorf("%+v — abandoned a QSO we were in the middle of", a)
|
||||
}
|
||||
if e.attempts != 0 {
|
||||
t.Errorf("attempts=%d — an exchange frame was counted as a call", e.attempts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheSlotIsNotWastedWhenTheTargetTurnsOutToBeBusy(t *testing.T) {
|
||||
e := on()
|
||||
if a := e.OnPeriod(period(0, cq("DX", NeedBand, -7))); a.Kind != DoReply {
|
||||
t.Fatalf("%+v", a)
|
||||
}
|
||||
// It answers somebody else, and a station of the same value is calling CQ
|
||||
// in the very same period. Waiting for the next one throws away a slot.
|
||||
a := e.OnPeriod(period(2, busy("DX", NeedBand, -7), cq("ER1CW", NeedBand, -8)))
|
||||
if a.Kind != DoReply || a.Decode.Call != "ER1CW" {
|
||||
t.Fatalf("%+v — the freed slot was not used", a)
|
||||
}
|
||||
if !strings.Contains(a.Reason, "cannot answer") || !strings.Contains(a.Reason, "calling ER1CW") {
|
||||
t.Errorf("reason %q says neither what was left nor what was taken", a.Reason)
|
||||
}
|
||||
|
||||
// Mid-over, it still waits: cutting our own transmission in half is worse
|
||||
// than losing the slot.
|
||||
e = on()
|
||||
e.OnPeriod(period(4, cq("DX", NeedBand, -7)))
|
||||
pp := period(6, busy("DX", NeedBand, -7), cq("ER1CW", NeedBand, -8))
|
||||
pp.TX = TXState{Transmitting: true}
|
||||
if a := e.OnPeriod(pp); a.Kind != DoHalt || !a.Soft {
|
||||
t.Errorf("%+v — replied over our own transmission", a)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTheFirstCallStartsWithNoMisses is the shack report: a CQ answered for the
|
||||
// first time, and the toolbar already reading two misses out of three.
|
||||
func TestTheFirstCallStartsWithNoMisses(t *testing.T) {
|
||||
e := on()
|
||||
// It calls CQ on an odd slot; we answer.
|
||||
if a := e.OnPeriod(period(1, cq("ER1CW", NeedBand, -2))); a.Kind != DoReply {
|
||||
t.Fatalf("%+v", a)
|
||||
}
|
||||
// The next period is OURS: we are transmitting to it, and of course it is
|
||||
// not decoded. That is not a miss.
|
||||
pp := period(2)
|
||||
pp.TX = TXState{Transmitting: true}
|
||||
e.OnPeriod(pp)
|
||||
if e.misses != 0 {
|
||||
t.Fatalf("misses=%d after our own transmit period", e.misses)
|
||||
}
|
||||
// Nor is a period of the wrong parity with nothing in it.
|
||||
e.OnPeriod(period(4))
|
||||
if e.misses != 0 {
|
||||
t.Fatalf("misses=%d — counted a period the station never transmits in", e.misses)
|
||||
}
|
||||
// ITS period, and it is not there: that is a miss.
|
||||
e.OnPeriod(period(3))
|
||||
if e.misses != 1 {
|
||||
t.Fatalf("misses=%d — the station's own silent period was not counted", e.misses)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user