Take serial numbers from a pool the whole entry shares

Each station worked out its next number as the highest in its log plus one,
so two operators typing at the same moment both sent the same number and
one of the two contacts was wrong in the sponsor's log.

SerialNumberPool holds the highest number taken per band, which is N1MM's
QSONumbers array. A number is taken at the first character of the callsign,
as N1MM takes it, and ReserveNr tells the other stations before the contact
is worked. A number at or below one this station has used is answered with
the whole pool, N1MM's QSONRS, and the station that is behind counts on from
there. The pool also goes out when a contest is opened and when the numbers
are started again.

Contest.NumbersByBand says whether the numbers run per band or as one
sequence for the station. CQ WPX runs one per band for a multi-two and a
multi-multi; every other contest here runs one sequence. The band index in
the message is N1MM's, so the array lines up with theirs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015NZkFxHde1iifAQemFTHxf
This commit is contained in:
2026-09-10 11:56:49 +00:00
co-authored by Claude Opus 5
parent 33c7631e17
commit 7ba0b44fc1
21 changed files with 677 additions and 40 deletions
+29 -13
View File
@@ -33,7 +33,8 @@ public sealed class ContestSession
Log = new ContestLog(contest, me, countries, instance.Entry) { History = History };
Log.Restore(store.Qsos(instance.ContestNumber));
Editor = new QsoEditor(contest, me, countries);
SentNumber = NextSentNumber();
Numbers = new SerialNumberPool { ByBand = contest.NumbersByBand(instance.Entry) };
SeedNumbers();
}
public Contest Contest { get; }
@@ -51,8 +52,11 @@ public sealed class ContestSession
/// What was published about the stations in this contest before it started.
public CallHistory History { get; }
/// Serial numbers count up across the station, not per radio.
public int SentNumber { get; private set; }
/// The serial numbers the whole entry has taken, one highest per band. A
/// number is taken from here when the operator starts a contact, not when
/// it is logged, and the other stations are told at once, so two operators
/// typing at the same moment cannot send the same number.
public SerialNumberPool Numbers { get; }
public string Operator { get; set; } = "";
@@ -74,10 +78,22 @@ public sealed class ContestSession
set
{
field = value;
SentNumber = NextSentNumber();
SeedNumbers();
NumbersRestarted?.Invoke(this, EventArgs.Empty);
}
}
/// The numbers have been started again, so a position holding one it took
/// earlier has to let it go.
public event EventHandler? NumbersRestarted;
/// The number a contact on this band would carry. Nothing is taken by
/// asking; `ReserveNumberOn` is what takes one.
public int NextNumberOn(Band? band) => Numbers.Next(band);
/// Takes the next number for a contact about to be worked on this band.
public int ReserveNumberOn(Band? band) => Numbers.Take(band);
public event EventHandler? Changed;
public event EventHandler<Qso>? Logged;
@@ -99,7 +115,7 @@ public sealed class ContestSession
{
Log.Replace(stored);
}
SentNumber = NextSentNumber();
Numbers.Record(stored.SentNumber, stored.Band);
Logged?.Invoke(this, stored);
Changed?.Invoke(this, EventArgs.Empty);
return stored;
@@ -189,8 +205,9 @@ public sealed class ContestSession
{
return false;
}
Log.Insert(store.Add(FromNetwork(qso)));
SentNumber = NextSentNumber();
Qso arrived = store.Add(FromNetwork(qso));
Log.Insert(arrived);
Numbers.Record(arrived.SentNumber, arrived.Band);
Changed?.Invoke(this, EventArgs.Empty);
return true;
}
@@ -252,16 +269,15 @@ public sealed class ContestSession
return edit;
}
/// The next serial number to send. A contest whose `ResetSNEverySession`
/// is set starts over in each session, so only the contacts of the session
/// we are in count.
private int NextSentNumber()
/// Fills the pool from the numbers the log already holds. A contest whose
/// `ResetSNEverySession` is set starts over in each session, so only the
/// contacts of the session we are in count.
private void SeedNumbers()
{
IEnumerable<Qso> counted = Contest.ResetsSerialNumbersEachSession
? Log.Qsos.Where(q =>
Contest.SessionOf(q.TimestampUtc) == Contest.SessionOf(DateTime.UtcNow))
: Log.Qsos;
counted = counted.Where(q => q.TimestampUtc >= SerialNumbersFrom);
return counted.Select(q => q.SentNumber).DefaultIfEmpty(0).Max() + 1;
Numbers.SeedFrom(counted.Where(q => q.TimestampUtc >= SerialNumbersFrom));
}
}