diff --git a/docs/n1mm-interop.md b/docs/n1mm-interop.md index cd6700b..c5ee1cf 100644 --- a/docs/n1mm-interop.md +++ b/docs/n1mm-interop.md @@ -100,29 +100,38 @@ from 2019 to 2026, 57,000 contacts, 28 contest names. Replaying it is how the scoring rules are checked — every contact is scored again from our rules and compared with the points and multiplier flags N1MM wrote. -26 of the 37 instances whose contest we have rules for now agree on every -contact and on the totals. What the check found and fixed: +29 of the 37 instances whose contest we have rules for agree on every contact +and on the totals. What the check found and fixed: | What | Was | |---|---| | ARRL DX and IARU read the wrong column | no multipliers at all, and IARU scored every contact 1 point | | a stored `" "` was read as a value | 181 contacts in one log scored as another continent | -| the CQ WW RTTY area list | `NF` and `LB` are that contest's own names for the Canadian areas, and were not counted | -| ARRL DX province spellings | `QB`, `NF` and `LB` count as Quebec and as Newfoundland and Labrador | -| a maritime mobile scored nothing | it counts for no country and still scores by continent | +| the Canadian area lists | ARRL DX and CQ WW RTTY count the 14 areas N1MM calls `48SDC14P`, Newfoundland apart from Labrador, and the postal codes map onto them | +| a maritime mobile scored nothing | it is scored from the call it signs, which is the country N1MM logs for it | +| a call the country file cannot place brought no multiplier | it counts, with an empty value, and all such calls share the one multiplier per band | | a contact in a mode the contest does not run | scored as if it were in the contest; N1MM scores it zero and keeps its multiplier | -What still differs, and why: +What still differs, and why. None of it is a rule: - **The country file of the day.** A 2019 log holds calls that today's - `wl_cty.dat` places elsewhere, and the multiplier moves to another contact - with it. This is most of the difference in the 2019 CQ WW SSB log. -- **N1MM counts a call it cannot place as a multiplier with an empty value**, - and we do not. Same as the WAE difference already written down. -- **N1MM counts `NF` and `LB` as two ARRL DX multipliers.** They are one - province, and we count one. -- **A maritime mobile brings no country multiplier here**, which is the - published CQ WW rule; N1MM counts one for it. + `wl_cty.dat` places elsewhere — `TO7O` was Mayotte then and is France now, + China was not split by call area yet — and the multiplier moves to another + contact with it. This is nearly all of the difference in the 2019 CQ WW SSB + log and all of it in the 2021 ones. - **Stale cached points.** N1MM caches points in the row and does not always work them out again after a callsign is corrected, so a handful of rows hold - a number that its own rules do not produce. + a number that its own rules do not produce. Two IARU rows and about six CQ WW + rows across 57,000 contacts. +- **A one-contact log** whose contact also sits in another instance of the same + contest. + +### What N1MM does that the published rules do not say + +Three of the fixes above follow N1MM rather than the rule as published, because +a log has to score the same in both programs: + +- ARRL DX counts `NF` and `LB` as two multipliers. They are one province. +- A maritime mobile counts for the country of the call it signs. CQ WW says it + counts for the zone alone. +- A callsign that places nowhere counts as a multiplier with an empty value. diff --git a/docs/unfinished.md b/docs/unfinished.md index da768fc..75b8084 100644 --- a/docs/unfinished.md +++ b/docs/unfinished.md @@ -55,9 +55,7 @@ any other message. disagreements are the country file of the day, not the rules: 4U1A counted as `4U1V` then and as `OE` in today's `wl_cty.dat`, `KP2BH` counted as `KP2` then and is listed as a US call now, and China was not split by call area yet. The -2022 to 2025 logs agree to the contact. One difference is ours on purpose: N1MM -counts a callsign it cannot place — `D1M` — as a multiplier with an empty -value, and we do not. +2022 to 2025 logs agree to the contact. **Telnet window: what N1MM has and this does not.** Left out: the special-calls list, and saving received spots to a database — N1MM keeps those spots in its diff --git a/src/Nonemm.Contests/Multipliers/StatesAndProvinces.cs b/src/Nonemm.Contests/Multipliers/StatesAndProvinces.cs index b58fe86..d99c073 100644 --- a/src/Nonemm.Contests/Multipliers/StatesAndProvinces.cs +++ b/src/Nonemm.Contests/Multipliers/StatesAndProvinces.cs @@ -22,37 +22,41 @@ public static class StatesAndProvinces }; /// The Canadian areas as the contests that split them name them: - /// Newfoundland apart from Labrador, and the territories spelled out. + /// Newfoundland apart from Labrador, and the territories spelled out. N1MM + /// calls this list `48SDC14P` — 48 states, DC and 14 provinces — and both + /// ARRL DX and CQ WW RTTY count them this way. public static readonly IReadOnlyList SplitCanadianAreas = - ["NWT", "NF", "LB", "PEI"]; + ["NB", "NS", "QC", "ON", "MB", "SK", "AB", "BC", "NWT", "NF", "LB", "NU", "YT", "PEI"]; - /// What ARRL DX counts for a DX station: the contiguous states, the - /// District of Columbia, and the Canadian provinces and territories. + /// What ARRL DX counts for a DX station: the 48 contiguous states, the + /// District of Columbia and those 14 areas. Alaska and Hawaii are DX for + /// this contest and count for none of it. public static readonly IReadOnlySet ArrlDxMultipliers = - new HashSet(ContiguousStates.Concat(["DC"]).Concat(Provinces), StringComparer.OrdinalIgnoreCase); + new HashSet( + ContiguousStates.Concat(["DC"]).Concat(SplitCanadianAreas), + StringComparer.OrdinalIgnoreCase); - /// The same province under another name. Canadian stations send the older - /// abbreviations as often as the postal codes, and ARRL DX counts the - /// province, not the spelling, so `NF` and `LB` are both Newfoundland and - /// Labrador and count once between them. N1MM counts them as two. + /// The same area under another name. A Canadian station sends the postal + /// code as often as the abbreviation the contest lists, and `NL` is read as + /// Newfoundland, which is the half of that province a station signing the + /// postal code is nearly always in; Labrador signs `LB`. private static readonly IReadOnlyDictionary OtherSpellings = new Dictionary(StringComparer.OrdinalIgnoreCase) { - ["NF"] = "NL", - ["LB"] = "NL", - ["NFL"] = "NL", + ["NL"] = "NF", + ["NFL"] = "NF", ["QB"] = "QC", ["PQ"] = "QC", - ["PEI"] = "PE", - ["NWT"] = "NT", + ["PE"] = "PEI", + ["NT"] = "NWT", }; - /// The province or state a received ARRL DX exchange names, or the exchange - /// as it was sent when it names none. + /// The area a received ARRL DX exchange names, or the exchange as it was + /// sent when it names none. public static string ArrlDxArea(string received) { string text = received.Trim(); - return OtherSpellings.TryGetValue(text, out string? province) ? province : text.ToUpperInvariant(); + return OtherSpellings.TryGetValue(text, out string? area) ? area : text.ToUpperInvariant(); } /// What CQ WW RTTY counts: the 48 contiguous states, DC, and the Canadian @@ -60,9 +64,7 @@ public static class StatesAndProvinces /// splits Newfoundland from Labrador and spells out `NWT` and `PEI`. public static readonly IReadOnlySet CqWwRttyAreas = new HashSet( - ContiguousStates - .Concat(["DC", "NB", "NS", "QC", "ON", "MB", "SK", "AB", "BC", "NU", "YT"]) - .Concat(SplitCanadianAreas), + ContiguousStates.Concat(["DC"]).Concat(SplitCanadianAreas), StringComparer.OrdinalIgnoreCase); public static bool IsStateOrProvince(string text) => diff --git a/src/Nonemm.Contests/Rules/CqWorldWide.cs b/src/Nonemm.Contests/Rules/CqWorldWide.cs index c6c6886..bdeb6e9 100644 --- a/src/Nonemm.Contests/Rules/CqWorldWide.cs +++ b/src/Nonemm.Contests/Rules/CqWorldWide.cs @@ -102,10 +102,7 @@ public sealed class CqWorldWide : Contest { found.Add(new Multiplier(1, qso.Qso.Zone.ToString(), band)); } - if (qso.CountryPrefix.Length > 0 && qso.Qso.Call.CountsForEntity) - { - found.Add(new Multiplier(2, qso.CountryPrefix, band)); - } + found.Add(new Multiplier(2, qso.CountryPrefix, band)); string home = qso.Qso.Section.Trim().ToUpperInvariant(); if (IsRtty && StatesAndProvinces.CqWwRttyAreas.Contains(home)) { diff --git a/src/Nonemm.Contests/Rules/Wae.cs b/src/Nonemm.Contests/Rules/Wae.cs index 7f8e093..bd85c59 100644 --- a/src/Nonemm.Contests/Rules/Wae.cs +++ b/src/Nonemm.Contests/Rules/Wae.cs @@ -84,14 +84,11 @@ public sealed class Wae : Contest public IReadOnlyList MultipliersFor(QsoContext qso) { int weight = WeightFor(qso.Band); - if (weight == 0 || !IsWorkable(qso) || !qso.Qso.Call.CountsForEntity) + if (weight == 0 || !IsWorkable(qso)) { return []; } - string value = MultiplierValue(qso); - return value.Length == 0 - ? [] - : [new Multiplier(1, value, qso.Band?.Name ?? "", weight)]; + return [new Multiplier(1, MultiplierValue(qso), qso.Band?.Name ?? "", weight)]; } public int TotalScore(ScoreTally tally, ContestEntry entry) => tally.Points * tally.TotalMultipliers; diff --git a/tests/Nonemm.Contests.Tests/ArrlDxTests.cs b/tests/Nonemm.Contests.Tests/ArrlDxTests.cs index 9631d1b..f8fb181 100644 --- a/tests/Nonemm.Contests.Tests/ArrlDxTests.cs +++ b/tests/Nonemm.Contests.Tests/ArrlDxTests.cs @@ -64,12 +64,15 @@ public class ArrlDxTests Assert.Empty(log.Judge(TestLog.Contact("VO2XYZ", section: "LB")).NewMultipliers); } - [Fact] - public void TheOlderQuebecAbbreviationIsQuebec() + [Theory] + [InlineData("QB", "QC")] + [InlineData("PE", "PEI")] + [InlineData("NT", "NWT")] + public void AnotherSpellingIsTheSameArea(string sent, string counted) { ContestLog log = new(new ArrlDx(ModeCategory.Phone), TestLog.Germany, TestLog.CountryFile); Assert.Equal( - "QC", - log.Judge(TestLog.Contact("VA2XYZ", section: "QB")).NewMultipliers.Single().Value); + counted, + log.Judge(TestLog.Contact("VA2XYZ", section: sent)).NewMultipliers.Single().Value); } } diff --git a/tests/Nonemm.Contests.Tests/CqWorldWideRttyTests.cs b/tests/Nonemm.Contests.Tests/CqWorldWideRttyTests.cs index 8b4201a..6676019 100644 --- a/tests/Nonemm.Contests.Tests/CqWorldWideRttyTests.cs +++ b/tests/Nonemm.Contests.Tests/CqWorldWideRttyTests.cs @@ -101,13 +101,14 @@ public class CqWorldWideRttyTests LogFor(TestLog.Germany).Judge(Contact("VO1XYZ", 5, area)).NewMultipliers, m => m.Index == 3 && m.Value == area); - /// A maritime mobile station belongs to no country. CQ WW counts it for the - /// zone alone, and the contact still scores by continent. + /// A maritime mobile station is at sea and belongs to no country. It is + /// scored from the call it signs, which is the country N1MM logs for it, + /// and it brings no WPX prefix. [Fact] - public void MaritimeMobileScoresButBringsNoCountry() + public void MaritimeMobileIsScoredFromTheCallItSigns() { Verdict verdict = LogFor(TestLog.Germany).Judge(Contact("IK2XYZ/MM", 15)); Assert.Equal(2, verdict.Points); - Assert.DoesNotContain(verdict.NewMultipliers, m => m.Index == 2); + Assert.Contains(verdict.NewMultipliers, m => m.Index == 2 && m.Value == "I"); } } diff --git a/tests/Nonemm.Contests.Tests/WaeTests.cs b/tests/Nonemm.Contests.Tests/WaeTests.cs index 2eaa85c..ab6a7f3 100644 --- a/tests/Nonemm.Contests.Tests/WaeTests.cs +++ b/tests/Nonemm.Contests.Tests/WaeTests.cs @@ -103,14 +103,16 @@ public class WaeTests Assert.Equal("UA0", multiplier.Value); } - /// N1MM counts a callsign it cannot place as a multiplier with an empty - /// value, which inflates the score. A call with no country is no - /// multiplier here. + /// A callsign the country file cannot place still counts, with an empty + /// value, so the log scores the same here as in N1MM. All the unplaceable + /// calls worked on a band share the one multiplier between them. [Fact] - public void ACallWithNoCountryIsNoMultiplier() + public void ACallWithNoCountryCountsOncePerBand() { ContestLog log = LogFor(Cw, TestLog.Germany); - Assert.Empty(log.Judge(On("D1M", 14_025)).NewMultipliers); + Assert.Equal("", Assert.Single(log.Judge(On("D1M", 14_025)).NewMultipliers).Value); + log.Add(On("D1M", 14_025)); + Assert.Empty(log.Judge(On("D2X", 14_025)).NewMultipliers); } [Fact]