fix(autocall): rest in overs, no phantom miss, and a readable auto readout
The rest between two series was two minutes — four overs on FT8, by which time the DX has worked four other callers and half the time has gone. It is now counted in the station's OWN overs and is one by default: seven calls, one over listened through, and it goes again if the station is still there. The deadline lands mid-cycle on purpose, so "sit out one over" means one over rather than depending on a millisecond of clock skew. A period the target was DECODED in is never counted as a miss. A period is judged more than once — decodes arrive in a burst and stragglers follow — and a later judgement holds a partial view of it, not evidence of absence: D2ACE answered in the very period the counter then read as a miss. The readout moved out of the Auto button and beside it. "Auto D2ACE 1/7 ·1/3" read as one number, and the control changed width every period. The callsign is now the biggest thing on the row, the calls and the missed periods each carry a label, the miss count appears only once there is one, and a station being waited for gets its own amber chip.
This commit is contained in:
@@ -244,24 +244,28 @@ func TestAReleasedStationYieldsToAnythingBetter(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAReleasedStationRestsBeforeItIsCalledAgain(t *testing.T) {
|
||||
// A series that ended in a brake is followed by ONE of the station's own overs
|
||||
// passed over — long enough to be a pause, short enough that the DX everybody
|
||||
// is chasing is still there when the calling resumes. It used to be two
|
||||
// minutes, which on FT8 is four overs: by then the DX has worked four other
|
||||
// callers, and half the time it has gone.
|
||||
func TestAReleasedStationSitsOutOneOfItsOvers(t *testing.T) {
|
||||
e := on()
|
||||
e.OnPeriod(period(0, cq("DX", NeedBand, -5)))
|
||||
tx := TXState{Transmitting: true, Msg: "DX " + me + " JN36"}
|
||||
for i := 0; i < 7; i++ {
|
||||
e.NoteTX(tx)
|
||||
}
|
||||
// Still the only thing on the air, and still resting: seven calls, a halt,
|
||||
// and the same station called again four seconds later is not a rest.
|
||||
for _, n := range []int{2, 4, 6} { // all inside the two minutes
|
||||
if a := e.OnPeriod(period(n, cq("DX", NeedBand, -5))); a.Kind != DoNothing {
|
||||
t.Fatalf("period %d: called again during the rest: %+v", n, a)
|
||||
}
|
||||
// Its very next over is passed over, whatever else is on the air: seven
|
||||
// calls, a halt, and the same station called again four seconds later is not
|
||||
// a rest, it is a stutter.
|
||||
if a := e.OnPeriod(period(2, cq("DX", NeedBand, -5))); a.Kind != DoNothing {
|
||||
t.Fatalf("called again during the rest: %+v", a)
|
||||
}
|
||||
// Two minutes later (period 8 × 15 s = 2 min past the give-up) it may go
|
||||
// again — the station is still there and nothing better is.
|
||||
if a := e.OnPeriod(period(9, cq("DX", NeedBand, -5))); a.Kind != DoReply {
|
||||
t.Errorf("after the rest: %+v, want the station called again", a)
|
||||
// And the one after that goes: still there, still wanted, and the operator
|
||||
// has listened through an over.
|
||||
if a := e.OnPeriod(period(4, cq("DX", NeedBand, -5))); a.Kind != DoReply {
|
||||
t.Errorf("after one over: %+v, want the station called again", a)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -929,20 +933,46 @@ func TestTheFirstCallStartsWithNoMisses(t *testing.T) {
|
||||
// rest of the session after two series of unanswered calls.
|
||||
func TestAWatchedCallsignIsNeverParked(t *testing.T) {
|
||||
spent := func(c Candidate) *Engine {
|
||||
e := New(Settings{Enabled: true, MaxRounds: 2, Rest: 0})
|
||||
e := New(Settings{Enabled: true, MaxRounds: 2})
|
||||
e.OnPeriod(period(0)) // the engine's clock, so the rest below is period time
|
||||
for i := 0; i < 2; i++ {
|
||||
e.giveUp(strings.ToUpper(c.Call), c) // a series ended by a brake
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// Period 8, well past the rest that follows a series: what is left is the
|
||||
// parking, and only the parking.
|
||||
plain := cq("PLAIN", NeedDXCC, 0)
|
||||
if a := spent(plain).OnPeriod(period(0, plain)); a.Kind == DoReply {
|
||||
if a := spent(plain).OnPeriod(period(8, plain)); a.Kind == DoReply {
|
||||
t.Errorf("%+v — a station whose series are spent was called again", a)
|
||||
}
|
||||
|
||||
dx := cq("ZD8GB", NeedDXCC, 0, watched)
|
||||
if a := spent(dx).OnPeriod(period(0, dx)); a.Kind != DoReply {
|
||||
if a := spent(dx).OnPeriod(period(8, dx)); a.Kind != DoReply {
|
||||
t.Errorf("%+v — a watched DXpedition was parked for the session", a)
|
||||
}
|
||||
}
|
||||
|
||||
// A period is judged more than once — the decodes arrive in a burst and
|
||||
// stragglers follow — and a later judgement of it holds a partial view, not
|
||||
// evidence that the station has gone. From the air: D2ACE answered in the very
|
||||
// period the counter then read as a miss.
|
||||
func TestAPeriodTheTargetWasSeenInIsNeverAMiss(t *testing.T) {
|
||||
e := on()
|
||||
if a := e.OnPeriod(period(1, cq("D2ACE", NeedBand, 13))); a.Kind != DoReply {
|
||||
t.Fatalf("%+v", a)
|
||||
}
|
||||
// Its own next period: decoded, so the exchange is under way.
|
||||
p := period(3, callsMe("D2ACE", NeedBand, 13))
|
||||
e.OnPeriod(p)
|
||||
if e.Status().Misses != 0 {
|
||||
t.Fatalf("misses=%d after being decoded", e.Status().Misses)
|
||||
}
|
||||
// The same period again, this time from a flush that carries only the
|
||||
// stragglers — the target is not among them.
|
||||
e.OnPeriod(period(3))
|
||||
if got := e.Status().Misses; got != 0 {
|
||||
t.Errorf("misses=%d — a second flush of a period it was seen in counted against it", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user