Commit Graph
15 Commits
Author SHA1 Message Date
erikbrockoandClaude Opus 5 7d9bc05e9a Add IOTA, the EU DX Contest, CQ 160 and Mexico RTTY
Four more of the contests in the station's log. IOTA, CQ 160 and Mexico RTTY
score every contact in it exactly as N1MM did.

IOTA pays 15 points for a station that sends an island reference and 2 for one
that does not, and counts each reference once per band and mode. CQ 160 is one
band, so its states and countries count once each. Mexico RTTY and CQ 160 both
take a state from one side of the contest and a serial or a zone from the
other, in the columns N1MM keeps them in.

The EU DX Contest is written from N1MM's class, including its list of what
counts as Europe: the European Union, its outermost regions and its overseas
territories, and nothing else. The station's own logs were made with the .udc
file, which scores and counts two things differently; docs/unfinished.md says
which.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-31 09:33:29 +00:00
erikbrockoandClaude Opus 5 3932f83147 Add the YOTA and OK/OM DX contests
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
2026-08-31 09:16:44 +00:00
erikbrockoandClaude Opus 5 5c0ca558c1 Score the last three cases the way N1MM scores them
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
2026-08-31 08:36:44 +00:00
erikbrockoandClaude Opus 5 04568d0ca3 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
2026-08-31 08:33:40 +00:00
erikbrockoandClaude Opus 5 9424c488ea Take the rest of what N1MM's QTC window does on CW
Reading the source again after this was tried turned up several things it had
wrong or left out.

The TU message went out only when reading a series out. N1MM sends it whichever
way the traffic went, and lets it name the series: {QTC} in it stands for the
header, so TU {QTC} 73 goes out as TU QTC 3/10 73.

The RX Ready button was hidden while sending and sent nothing on CW. It is
N1MM's QRV button: reading out it says R U QRV and keys QRV?, taking down it
says QRV and keys QRV.

The fields were asked for again with shift and Enter, which was made up. N1MM
uses the number keys, and writes them under the lines: 1, 2, 3 and 4 send the
time, call, serial or header of the last line again while reading out, and
shift with 1, 2 or 3 asks for that field while taking down.

The buttons now carry N1MM's names for the direction the traffic is going —
R U QRV, Snd Hdr, Snd1 to Snd10, Exit reading out; QRV, Hdr Agn, Agn1 to Agn10,
Exit taking down — and the Cfm column and Clear are hidden while reading out,
as N1MM hides them. The separate Send Hdr button is gone: N1MM has one button
there that sends the header one way and asks for it the other.

The log window gained N1MM's QTC? column for a contest that carries QTC
traffic. It holds the series, so a QTC row can be told from a contact; before
this a QTC row read as an ordinary one with odd values in the report columns.

Driven under Xvfb against a fake cwdaemon. Reading out: QRV?, QTC 3/2,
2319 DL7GGG 03, then 2319 and QTC 3/2 from the number keys, and
TU QTC 3/2 73 on Exit. Taking down: QRV, AGN TIME, AGN CALL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-30 23:23:22 +00:00
erikbrockoandClaude Opus 5 ff46cb00ed Put N1MM's status bar along the bottom of the entry window
Three parts, as N1MM has them: what just happened on the left, the contacts
and multipliers in the middle, the score on the right.

The left says where the call being typed is from, in N1MM's own shape:
OM: EU -> Slovak Republic, Zn 15. The zone is the one the contest counts in, so
Contest.UsesItuZones answers that the way N1MM's ZoneType does, and IARU
overrides it. A message from something the operator just did stands there until
the next keystroke.

The middle is N1MM's breakdown: the contacts and then one number per multiplier
the contest counts, separated by slashes, and No Score before the first
contact. Points are not in it, because they are not in N1MM's; the score window
has them.

Those numbers were in the line at the top of the window, which now carries the
contest name alone, and the country was in the bar under the callsign box,
which now says only what working the call would bring. Neither is said twice.

Looked at under Xvfb: the country line for OM3KFF and then G4AB, 10/7/8 and
300 beside them, and a Mark message standing in the left until the next
keystroke.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-30 22:46:13 +00:00
erikbrockoandClaude Opus 5 13a1acfcab Lay the band panel out the way N1MM lays it out, and colour it
N1MM's band panel is a row per band and a column per mode the contest runs,
with every cell painted by what the callsign being typed would be worth there:
red for one new multiplier, green for more than one, blue for points, grey for
a station already worked on that band and mode. With nothing typed every cell
is grey. That is the panel's point — it says where a station is still needed
without working the bands one at a time — and this had only the frame.

BandPanel holds the rule and is unit-tested without a window. The bands are the
six contest bands, with 30, 17 and 12 metres added for general logging: in N1MM
that is the one entry of the hundred and thirty-five that shows the WARC bands,
so Contest.ShowsWarcBands says so and GeneralLogging overrides it. The columns
are the modes the contest runs, so CQ WW CW has one column and IARU has two,
rather than always showing CW and phone. A digital entry gets one DIG column
where N1MM has RTTY and PSK: dupes and points here go by mode category, and
those two are the same category.

The labels are N1MM's: the metres alone up to 10, then the unit from 6m up. The
band the radio is on is ringed rather than filled, as N1MM rings it, so the
ring does not hide the colour underneath. The cells are labels with a click
handler rather than buttons, so the theme cannot paint over the colour under
the pointer.

Text on a filled cell is black or white by N1MM's rule: light is the three
channels adding up to more than 384.

Looked at under Xvfb in CQ WW CW: one CW column, six bands, grey with nothing
typed, and with a JA typed red on every band but 20, where the zone and country
are already claimed, and which carries the ring.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-30 21:42:45 +00:00
erikbrockoandClaude Opus 5 8956c2fd22 Take the rest of what reading N1MM turned up
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>
2026-08-28 10:23:30 +00:00
erikbrockoandClaude Opus 5 9ae5078c84 Take WAE QTC traffic down
Ctrl+Z opens the QTC window for the station being worked, which is the key N1MM
puts it on, and the window is laid out the way N1MM lays it out: the header,
ten lines of time, callsign and serial number, an Agn and a Cfm button beside
each, Clear, Close and Cancel underneath, and the same colours — green for a
line that will be saved, red for one still empty, yellow for one that cannot be
read. Enter and Tab move forward and space moves within a line, so a line can be
walked while listening to it.

Sending, the lines are filled from the log and cannot be edited: the contacts
not reported to anybody yet, oldest first, never the station being worked, and
never past the ten any station may have. The rule for what has been reported is
N1MM's — a contact counts as reported when a QTC row carries its call and the
serial we sent it — so the pair is kept in the row the way N1MM keeps it.

Which way traffic goes is not the operator's choice on CW and SSB: Europe
receives and the rest of the world sends. On RTTY it goes both ways, and there
the window carries a switch. That is the one piece of the workflow that is ours
rather than N1MM's, which toggles a QTC mode outside the window instead.

Nothing here transmits. N1MM sends the lines on CW and RTTY and plays recorded
voice messages on SSB; we have no QTC message templates, no voice keyer and no
digital window, so Agn, Cfm and RX Ready move the cursor and the operator sends
by hand.

Running it caught what the tests could not: focus set in the constructor does
not stick, so the first thing typed into a freshly opened window was dropped and
every field after it took the value of the one before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 00:02:47 +00:00
erikbrockoandClaude Opus 5 8961677354 Score Worked All Europe, QTC traffic and all
WAE is two contests in one. The QSOs are scored like any other, except that a
multiplier is worth four on 80 metres, three on 40 and two on the high bands, so
Multiplier grew a weight and ScoreTally sums weights rather than counting.
Europe works the rest of the world and the rest of the world works Europe; on
RTTY that rule is dropped and everybody works everybody.

The other half is QTC traffic: one station reads back contacts it has already
made, ten to a series, and every line is a point for both stations. Those lines
live in the log as rows of their own, exactly where N1MM puts them — Exchange1
says SQTC or RQTC, the reported contact's time, call and number go in the sent
report, received report and sent number columns, and the series in misc text.
So a log written by either program opens in the other with its traffic intact.
A QTC row is not a worked station: Contest.IsContact says so, and ContestLog
keeps such rows out of the dupe index and the multiplier index while still
counting their points. Cabrillo writes them as QTC: records.

Checked against eight real WAE logs rather than against my own reading of the
rules. Points agree with N1MM on all eight, to the contact. Weighted
multipliers agree on the six logs from 2022 on; the two older ones differ only
where the country file has since changed its mind, and in one place on purpose:
N1MM counts a callsign it cannot place as a multiplier with an empty value.

That comparison turned up two bugs of ours. A KG4 call is Guantanamo Bay only
when it is three or five characters long — KG4NE is, KG4IGC is an ordinary US
call — and the country file cannot say so, so every program that reads it
carries the rule. And Callsign is a record holding a list of modifiers, so the
generated equality compared that list by reference and two parses of one call
came out different.

Entering QTCs while operating is not there yet; the window is next.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 23:48:47 +00:00
erikbrockoandClaude Opus 5 86c01f246f Score CQ WW RTTY as its own contest
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>
2026-08-27 23:37:54 +00:00
erikbrockoandClaude Opus 5 da9884ebf5 Open a log N1MM wrote
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>
2026-08-27 16:27:35 +00:00
ErikandClaude Opus 5 d96cbe146b Write what N1MM needs before it will open a log
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>
2026-08-27 11:15:48 +00:00
ErikandClaude Opus 5 ef631f1dc4 Add the rest of the major contests, .udc files and the registry
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>
2026-08-27 10:35:28 +00:00
ErikandClaude Opus 5 0f84bc8972 Add the core, contest, storage and format layers
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>
2026-08-27 10:27:51 +00:00