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

@@ -79,3 +79,50 @@ opened. The name carries the mode, so it beats whatever `ModeCategory` says.
Older N1MM versions wrote the CW running of CQ WW as plain `CQWW`. That name is
still in the registry, so those logs open.
## Which column an exchange lands in
N1MM keeps the received exchange of ARRL DX, IARU and WAE in `Sect`, not in
`Exchange1`, whatever the entry window calls the box. A contest that reads the
wrong column scores every contact as if the operator had typed nothing, so the
rule reads the column N1MM writes and the entry window points its box at the
same place.
N1MM also writes a single space into a text column it has nothing for, so
`Continent` comes back as `" "` rather than empty. Reading it as a value rather
than as empty makes every contact look like another continent. The store trims
what it reads.
## Scoring checked against a real log
`om5m.s3db` in the repo root is a station's own N1MM log: 78 contest instances
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:
| 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 |
| 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:
- **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.
- **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.

View File

@@ -1,7 +1,7 @@
# What is not finished, and what has not been tested
Two separate lists. A feature can be finished and untested, or half-built and
exercised every day. Written 2026-08-27.
exercised every day. Written 2026-08-27, updated 2026-08-31.
## Never run against the real thing
@@ -107,10 +107,24 @@ needs QTC traffic, IOTA needs island references as multipliers, and CQ WW RTTY
needed a third multiplier and its own points table because it shares a name
with the CW and SSB running of the contest.
What `.udc` cannot say here yet: an exchange that differs by the other
station's country (the OK/OM district against a serial number from everyone
else), points that depend on a zone or a distance, and multiplier sources
beyond country, prefix, zone, section, exchange, grid and continent.
`.udc` settings that are still passed over. `CallHist` as a multiplier source,
which takes the value from the call history file. `BonusPoints2`, which reads
the bonus callsigns from a file. `MultiplierBands`, `MultWindowType` and
`QsoErrorString`, which are about the windows rather than the score.
`CabrilloString`, `CabrilloFormat` and `GenericPrintString`, so a contest whose
Cabrillo line is not callsign, report and one exchange value comes out in the
default shape. The session, off-time and band-change settings
(`MultipleSessions`, `MinimumOffTime`, the `…BandChange…` family,
`DupeQSOMinutesAgo`) are read by nothing, so a contest with periods is logged
as one long session.
N1MM's `.udc` format has no way to say that the exchange itself differs by the
other station's country, as the OK/OM DX contest asks a district from OK and OM
stations and a serial number from everyone else. That one needs code, as it
does in N1MM, and `Contest.SkipsField` is where it goes. A contest scored by
what this station sends — YOTA counts the sent exchange — needs code too, and
reads `QsoContext.Entry`, which carries the entry categories and the sent
exchange.
## Known rough edges