Read the rest of what a .udc file says, and score what N1MM scores

Two pieces of work on the scoring engine.

The .udc reader now takes N1MM's published scoring vocabulary: the full
PointsPerContact condition set, the PointsMultBy family, PowerMult, BonusPoints,
the multiplier sources and the settings that say which stations bring a
multiplier in. MultMult is a weight rather than a switch, as in N1MM's
ComputeScore. The entry categories and the sent exchange reach the rules in a
new ContestEntry, so a contest can score by the power category it is entered in
or by what this station sends.

Then every contest in a real N1MM log was scored again from these rules and
compared with the points and multiplier flags N1MM wrote. That found five bugs:
ARRL DX and IARU read the exchange from the wrong column, a stored " " was read
as a value rather than as empty, CQ WW RTTY's own Canadian area names were not
counted, a maritime mobile scored nothing, and a contact in a mode the contest
does not run scored as if it were in the contest.

docs/n1mm-interop.md has the check and what still differs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
This commit is contained in:
2026-08-31 08:33:40 +00:00
parent 9424c488ea
commit 04568d0ca3
28 changed files with 1088 additions and 142 deletions

View File

@@ -12,7 +12,7 @@ public class ArrlDxTests
public void DxWorkingWvieScoresThree()
{
ContestLog log = LogFor(TestLog.Germany);
Assert.Equal(3, log.Judge(TestLog.Contact("K1ABC", exchange: "CT")).Points);
Assert.Equal(3, log.Judge(TestLog.Contact("K1ABC", section: "CT")).Points);
}
[Fact]
@@ -33,8 +33,8 @@ public class ArrlDxTests
public void DxCountsStatesAndProvinces()
{
ContestLog log = LogFor(TestLog.Germany);
Assert.Equal("CT", log.Judge(TestLog.Contact("K1ABC", exchange: "CT")).NewMultipliers.Single().Value);
Assert.Empty(log.Judge(TestLog.Contact("K1ABC", exchange: "XX")).NewMultipliers);
Assert.Equal("CT", log.Judge(TestLog.Contact("K1ABC", section: "CT")).NewMultipliers.Single().Value);
Assert.Empty(log.Judge(TestLog.Contact("K1ABC", section: "XX")).NewMultipliers);
}
[Fact]
@@ -48,8 +48,28 @@ public class ArrlDxTests
public void MultipliersCountOncePerBand()
{
ContestLog log = LogFor(TestLog.Germany);
log.Add(TestLog.Contact("K1ABC", exchange: "CT"));
Assert.Empty(log.Judge(TestLog.Contact("K2ABC", exchange: "CT")).NewMultipliers);
Assert.Single(log.Judge(TestLog.Contact("K2ABC", 7_025, exchange: "CT")).NewMultipliers);
log.Add(TestLog.Contact("K1ABC", section: "CT"));
Assert.Empty(log.Judge(TestLog.Contact("K2ABC", section: "CT")).NewMultipliers);
Assert.Single(log.Judge(TestLog.Contact("K2ABC", 7_025, section: "CT")).NewMultipliers);
}
/// A Canadian station sends the postal code or the older abbreviation, and
/// Newfoundland and Labrador is one province either way. N1MM counts `NF`
/// and `LB` as two multipliers, which is two for one province.
[Fact]
public void NewfoundlandAndLabradorCountOnce()
{
ContestLog log = new(new ArrlDx(ModeCategory.Phone), TestLog.Germany, TestLog.CountryFile);
log.Add(TestLog.Contact("VO1XYZ", section: "NF"));
Assert.Empty(log.Judge(TestLog.Contact("VO2XYZ", section: "LB")).NewMultipliers);
}
[Fact]
public void TheOlderQuebecAbbreviationIsQuebec()
{
ContestLog log = new(new ArrlDx(ModeCategory.Phone), TestLog.Germany, TestLog.CountryFile);
Assert.Equal(
"QC",
log.Judge(TestLog.Contact("VA2XYZ", section: "QB")).NewMultipliers.Single().Value);
}
}