The entry window had a fixed set of boxes with a Name and Comment box beside
the exchange. N1MM lays out one row of boxes per contest and nothing else, so
this does the same: general logging keeps Name and Comment, and a contest gets
the boxes it exchanges.
Two new members on `Contest` carry the cases where the boxes and the log
columns are not the same list:
- `FromExchange1` spreads what was typed in one box across the log columns.
Where the other station sends a serial or a district code depending on where
it is, N1MM gives it one box and sorts the value into the number, section or
zone column from there, so the score and the Cabrillo file read it from the
same column N1MM does.
- `LogFieldsFor` gives the log window its own column list. Sweepstakes takes
the whole exchange in one box and logs it in four columns; `SweepstakesExchange`
parses `72 A 74 SCV` in any order, reading from the last word back as N1MM
does.
The sent serial is now marked as not required and space steps over it along
with the two report boxes, because nobody types the number going out.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015NZkFxHde1iifAQemFTHxf
Both are in the station's log and neither could be opened. The rules are
written from the published ones and checked by replaying the log.
YOTA scores a contact by the age the other operator sends: 13 points down to 10
for anyone 25 or under, whatever the continent, and 1 point or 3 for everyone
else. Every age worked on a band is a multiplier. The 2024, 2025 and 2026 logs
come out to the contact.
OK/OM DX is the first contest whose exchange differs by the other station's
country: a county from OK and OM stations, a serial number from everyone else,
in the two columns N1MM keeps them in, and `SkipsField` walks the entry window
past the box that does not apply. Points depend on which side the operator is
on, and Czechia and Slovakia count as the two countries they are. The 2023 log
comes out to the contact.
The Cabrillo exchange now gets the entry, because YOTA sends an age that only
the contest setup knows.
Two differences are left in the log, both from the .udc files the operator
used: their YOTA file only lists ages 7 to 25, so a younger operator scored as
an adult, and one OK/OM log was made with the file for stations outside OK and
OM while signing an OM call.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
The three differences left after replaying the log were ours on purpose. A log
has to score the same in both programs, so they now follow N1MM:
- ARRL DX and CQ WW RTTY count the 14 Canadian areas N1MM lists as `48SDC14P`,
which keeps Newfoundland apart from Labrador. A station sending the postal
code is counted as the area the contest names.
- A maritime mobile is scored from the call it signs. CQ WW says it counts for
the zone alone; N1MM gives it the country, and so do we.
- A callsign the country file cannot place counts as a multiplier with an empty
value, so every unplaceable call worked on a band shares one multiplier.
29 of the 37 contest instances in the log now agree with N1MM on every contact
and on the totals. What is left is the country file of the day and rows whose
cached points N1MM never worked out again after a call was corrected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
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
The call frame. Above the callsign box, N1MM writes which spotted station the
radio is sitting on, coloured like the bandmap colours it, or CQ-Frequency when
this is the frequency the last CQ went out on. Space with an empty callsign box
takes the call out of the frame and into the box, but only while searching:
TabPressed does that when the run box is unchecked, because a running station is
answering callers rather than chasing the one under its VFO. How close counts as
sitting on it is N1MM's tuning tolerance, 300 Hz, now a setting.
Sh/dx while unassisted. TelnetClient.Output refuses any command holding SH/DX
when the entry is not assisted, and the telnet window does the same now.
Whether an entry is assisted follows N1MM's own rule: assisted, multi-operator
or checklog may use spots, a single operator who did not say assisted may not.
The node's flavour. Click_LogonButton reads DXSpider, AR-Cluster, CC Cluster or
GoCluster out of the banner; ClusterClient reads the same words and the window
shows what it found. It changes nothing yet — the commands it would change are
ones this does not send — but the node says so and now we know.
CQ WW RTTY's state box. NextTab only stops on SectionText when the station is
US or Canadian, because nobody else sends a state, so a contest can now say a
box is not for this station and the walk steps over it. The box is still there
to type in.
The telnet button editor shows as many buttons as are defined, with an empty row
to write the next one in, rather than N1MM's twelve.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CQWWRTTY resolved to the built-in CQ WW with the mode set to digital, and then
scored it as though it were the CW running: a contact inside your own country
worth nothing, two multiplier types, and CQ-WW-SSB in the Cabrillo header. Every
one of those is wrong for RTTY, and wrong quietly, which is the worst way.
RTTY is a different contest wearing the same name. Every contact scores — 1
inside your own country, 2 on your own continent, 3 off it. The exchange carries
the state or province after the zone, and that is a third multiplier per band.
A station outside the US and Canada sends DX there, so the box is not required
and DX brings no multiplier.
The state box being optional is deliberate: most of the log is DX stations, and
holding up an entry for a box that will hold DX costs more than it is worth.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three things stopped it.
A contest is keyed by ContestNR, not by ContestID. ContestID is only the
ContestInstance table's primary key; DXLOG.ContestNR refers to ContestNR, which
is what N1MM's SQLWhereString matches on. The two agree in a fresh log and
drift apart in one that has been used for a while, so we were reading one
contest's header with another contest's contacts. In a real log of 56284
contacts, joining on ContestID disagreed with the contact's own ContestName
52029 times; joining on ContestNR disagreed 109 times.
N1MM names a contest per mode: CQWWCW, CQWWSSB, CQWWRTTY, never plain CQWW.
Our contests now use those names, and N1mmContestNames turns one back into a
contest and a mode when a log is opened. The name carries the mode, so it beats
the ModeCategory column. Plain CQWW still opens, because older versions wrote
it that way.
The log columns were sized by counting characters, which was too narrow for the
headers: those are drawn in the theme's font, not the grid's monospace. The
grid sizes them now, with the character count as a floor so a column of short
values does not collapse.
Bandmap spots now age from when they arrived rather than from the time written
in them. A node with a wrong clock, or one replaying its backlog on connect,
emptied the bandmap as fast as it filled it.
Checked by opening a real 56284-contact N1MM log under Xvfb and reading the
contest and contacts back.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Checked by opening a log this program wrote in N1MM 1.0.11031. Three things it
rejected:
- a log naming a contest with no row in the Contest table throws "No current
row" in Contest.FromRow, so the definition row is written whenever a contest
is opened;
- an empty overlay category is answered with "Invalid Overlay Category:", so an
entry with no overlay now says "N/A", and the dialog offers N1MM's list;
- the sent exchange omits the report, which is what N1MM's own contest dialog
asks for.
With those three fixed N1MM opens the log and shows the contacts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ARRL DX, IARU HF, Sweepstakes, RTTY Roundup, NAQP and general logging join
CQ WW and CQ WPX. The Cabrillo QSO line is now built column by column by the
contest, because Sweepstakes puts the callsign after the serial number where
everyone else puts it first.
User-defined contests read the subset of N1MM's .udc settings that covers the
exchange, the dupe rule, points and up to three multipliers.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Frequencies, bands, modes, callsigns, grid squares and the country file live in
Nonemm.Core. Nonemm.Contests holds the scoring engine and CQ WW and CQ WPX.
Nonemm.Storage writes N1MM's DXLOG schema, and Nonemm.Formats writes Cabrillo
3.0 and reads and writes ADIF.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>