Commit Graph
72 Commits
Author SHA1 Message Date
erikbrockoandClaude Sonnet 5.5 393b73f87a Write N1MM's own Contest table rows for the built-in contests
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 03:47:29 +00:00
erikbrockoandClaude Sonnet 5.5 65178b2d89 Add the Broadcast Data dialog and send N1MM's score breakdown
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 03:42:19 +00:00
erikbrockoandClaude Sonnet 5.5 c01032a2cc Add a sound recorder that writes several devices to one Opus file
nonemm-recorder is a separate program (src/Nonemm.Recorder.App) over a
library (src/Nonemm.Recorder). Each row in its list is one channel of one
device, and each row becomes one channel in an Ogg Opus file: 60 ms frames,
complexity 9, 32 kbps per channel by default.

- A device that is not connected keeps its row, in red, and writes silence.
  It is recorded from the moment it returns, also during a recording.
- Output devices are recorded as loopback: WASAPI on Windows, the
  "Monitor of" sources on PulseAudio and PipeWire.
- A headphone button plays one channel, in mono, on the default device.
- The rows, bitrate and folder are saved when the window closes.

SoundFlow 1.4.2 numbers its backend enum one higher than the miniaudio it
ships, so asking for PulseAudio gave ALSA and Wasapi gave DirectSound.
SoundFlowBackend lowers the value by one and checks which backend started.

Output devices and live capture are not tested on real hardware. The
tests use a fake sound system and decode the written file.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 23:47:45 +00:00
erikbrockoandClaude Sonnet 5.5 c18d79ba5a Run the digital bridge on Windows
The bridge launched its helper only through Wine. It now starts any
process, so on Windows it runs the helper directly. WineBridgeChannel is
renamed ProcessBridgeChannel.

WindowsControlSetup sets up the control side on Windows. The digital
dialog, AppSession, README and docs/digital-bridge.md describe the
Windows setup. publish-windows.sh builds a Windows release.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 21:56:47 +00:00
erikbrockoandClaude Opus 5 b5043506ae Fill in the macros from N1MM's function key page
Going down N1MM's published list, the ones a contest operator would miss:

| | |
|---|---|
| `{LASTEXCH}` | what the last station sent, built from the boxes the contest asks for |
| `{ROVERQTH}` | where a rover is now |
| `{TIME}`, `{DATE}`, `{TIMEGMT}`, `{DATEGMT}` | the clock and the calendar |
| `{ESC}`, `{CTRL-A}` to `{CTRL-Z}` | the control characters a TNC reads as commands |
| `{CTRLHOME}` | the cursor back in the callsign box |
| `{F1}` to `{F12}` | another of this radio's keys, where `{CTRLF1}` is the other radio's |
| `{CQ}` | makes any key a CQ key: it starts running and carries alternating CQ on |
| `{DIGQTCS}`, `{DIGQTCR}` | opens the WAE QTC window the way asked for |
| `{CLEARRIT}` | the receive offset back to zero, which is rigctld's `J 0` |
| `{FORCELOG}` | logs what is in the boxes, holes and all |

`{FORCELOG}` is the one that gives something up. `LogContact` refuses an entry
that is short or holds a value the contest will not take, so that a caller who
has not checked cannot log nonsense; `LogContactAnyway` is the way past it,
because a contact in the log with a hole in it can be filled in afterwards and
one that was never logged cannot. It still needs a callsign, and the status
line says the row will need fixing before the log goes in.

A key that names itself would send for ever, so a key sending another key
ignores the macro a second time down.

What is still passed over is written down in the README and unfinished.md.
Advanced voicing is the one that matters: N1MM says a callsign or a serial by
playing a recording per letter out of `LettersFiles`, which is why `!` and `#`
say nothing on phone here and why F5 is empty in N1MM's own SSB file too. The
three packet BBS date formats are passed over because there is nothing here to
check a guess at them against.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
2026-09-11 08:24:34 +00:00
erikbrockoandClaude Opus 5 082fa75a40 Play a recording on phone, and record it in a window
A phone function key names a wav file instead of words, which is what N1MM's
own SSB file holds: `F1 CQ,{WAVDIR}\Cq.wav`. `{WAVDIR}` is the folder named
after whoever is at the microphone, a name is taken relative to `Wav`, and
commas play several files in a row. An SSB `.mc` file written for N1MM plays
here.

Window ▸ Voice Keyer lists every recording the messages name, says which of
them are there for the operator picked at the top, plays one, and records over
one. The recording is written beside the wanted file and moved over it at the
end, so a recorder that will not start takes nothing away.

`CommandSoundCard` runs `aplay` and `arecord`, and both commands are settings:
the audio path belongs to whatever the station runs, and a library would pick
one of them for the operator. A recorder is stopped with an interrupt so it
closes its wav file, and killed only if it ignores that. PTT is cwdaemon's
`<ESC>a` for as long as the recording plays, off for a station on VOX.

Two things follow. The QTC window sends on SSB: N1MM's four recordings on the
Ready, Agn, Cfm and Close buttons, with the lines still read out by the
operator. And alternating CQ duels on phone, because the voice keyer says when
a recording has finished — which it had to, since the CQ message is picked by
the radio's mode and would otherwise have been keyed as Morse.

Nothing here has been played on a radio: this machine has no sound card, so
what is tested is the program being run, the file filled in, the exit code and
the interrupt, with shell commands standing in for the two.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
2026-09-11 07:51:58 +00:00
erikbrockoandClaude Opus 5 dae1f4f197 Clear the stale items out of the docs
The scoring audit says which of its findings are fixed and which are still
open, its line numbers point at where the code is now, and the order of what
to do next is what is left rather than what is done.

In unfinished.md: Send All was listed as missing 500 lines above the section
that describes it, three TypeAhead methods had been renamed, the station
number clash is guarded and the paragraph saying it is not was next to the one
saying it is, and the registry holds twenty-six contests rather than
twenty-five. The claim that every instance of the station's logs scores
contact for contact is replaced by what the replay of 2026-09-06 actually
found.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
2026-09-11 07:51:45 +00:00
erikbrockoandClaude Opus 5 b5d9f1e21c Name the All Asian multiplier after what it counts
The score window and the stored Contest.Multiplier1Name row both said
"Prefixes" for an Asian entrant, which counts DXCC entities.

Contest.MultiplierNames is now MultiplierNamesFor(StationInfo me), the
same shape as ExchangeFieldsFor. Every other contest returns the same
list it did before; All Asian returns "Countries" from Asia and
"Prefixes" from everywhere else. ContestDefinitions.For takes the
station as well, because that name is written to the log for N1MM.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
2026-09-10 20:47:51 +00:00
erikbrockoandClaude Opus 5 f5e3d9ed8a Score All Asian from the entrant's own side
All Asian is two contests in one class and only one of them was written.
An Asian entrant was paid three times the band table for every contact,
including Asian ones, could work its own country, and counted WPX
prefixes where the rules count DXCC entities.

AllAsian now asks where the entrant is. An Asian station works anybody
outside its own country, gets the band table once for Asia and three
times for the rest, and counts entities. Everybody else works Asia only
and counts prefixes, as before.

Maritime mobiles follow the JARL rule rather than N1MM, which applies it
to non-Asian entrants alone: a station at sea makes a valid contact with
Asian stations only, is paid the Asian rate wherever it is, and counts
for no multiplier either way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
2026-09-10 20:43:43 +00:00
erikbrockoandClaude Opus 5 04b6f14639 Mark the Oceania finding fixed
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
2026-09-10 20:38:07 +00:00
erikbrockoandClaude Opus 5 211298e04c Let the walk and the completeness check read one rule
A box the walk between the entry boxes steps over cannot be typed in, so it
must not hold the contact back either. `EntryFields.IsComplete` counted every
box marked required, while `MoveFocus` stepped over the ones the contest does
not ask this station for, so a contest with a box for some stations only
could lock the operator out of his own log.

The seven contests the scoring audit named do not do that any more —
`bae40bb` gave each of them the boxes N1MM shows, and fifteen reproductions
across all seven and both station types log cleanly — but the rule they broke
was still there to break again. Completeness now takes the same
applicability rule the cursor uses, and `OperatingPosition` answers it in one
place for the entry window, the ESM window and logging alike.

The audit is marked up for the three findings that are already fixed and for
item 14, which turns out to be what N1MM does rather than a fault.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
2026-09-10 20:33:30 +00:00
erikbrockoandClaude Opus 5 e56d6096f3 Say in the docs that the RTTY QTC messages are there now
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
2026-09-10 20:04:19 +00:00
erikbrockoandClaude Opus 5 980ddb586f Count from the log again on Reset Serial Numbers
The button started the numbers again at one. N1MM's manual says it "sets
serial numbers on all computers to the highest value in any of the logs,
plus 1", and its own handler re-reads the highest number per band out of
the log, so a mixed network came apart at exactly the point the button is
meant to repair: the N1MM stations counted on from the highest number and
this one started at one, sending numbers that had already gone out.

ContestSession.RestartNumbers fills the pool from the log, which is where
the reset now lands. SerialNumbersFrom is gone with it, and so is the pool
this station sent afterwards: N1MM sends none, because each station works
the same number out of its own copy of the log.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015NZkFxHde1iifAQemFTHxf
2026-09-10 12:28:03 +00:00
erikbrockoandClaude Opus 5 7ba0b44fc1 Take serial numbers from a pool the whole entry shares
Each station worked out its next number as the highest in its log plus one,
so two operators typing at the same moment both sent the same number and
one of the two contacts was wrong in the sponsor's log.

SerialNumberPool holds the highest number taken per band, which is N1MM's
QSONumbers array. A number is taken at the first character of the callsign,
as N1MM takes it, and ReserveNr tells the other stations before the contact
is worked. A number at or below one this station has used is answered with
the whole pool, N1MM's QSONRS, and the station that is behind counts on from
there. The pool also goes out when a contest is opened and when the numbers
are started again.

Contest.NumbersByBand says whether the numbers run per band or as one
sequence for the station. CQ WPX runs one per band for a multi-two and a
multi-multi; every other contest here runs one sequence. The band index in
the message is N1MM's, so the array lines up with theirs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015NZkFxHde1iifAQemFTHxf
2026-09-10 11:56:49 +00:00
erikbrockoandClaude Opus 5 de8edafa0c Add the scoring review against om5m.s3db
The review of 6e24e00 against the decompiled reference and a replay of the
56,284 rows of om5m.s3db, with the per-instance comparison in the CSV.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015NZkFxHde1iifAQemFTHxf
2026-09-09 20:12:51 +00:00
erikbrockoandClaude Opus 5 6e24e00ec8 Copy the statistics table and graph to the clipboard
Copy All takes whichever tab is open, as N1MM's does: the table off the table
tab, a picture of the graph off the graph tab. Copy Table as Text and Copy
Graph as Image do either one from either tab.

`StatisticsTable` lays the pivot out as the window prints it — the headings,
the rows, the Total line — and pads it into columns for the text form, so the
grid, the clipboard and Print to File all say the same thing.

X11 asks for a picture when the other program pastes rather than when it is
copied, so the bitmap given to the clipboard is held until the clipboard is
given a different one. Disposing it at the end of the copy killed the program
on the next paste; the table pasted into a second Avalonia program reads back
lined up, and the picture reads back as a 916x438 bitmap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdAYHcdRktqKry7nk414TU
2026-09-04 07:59:22 +00:00
erikbrockoandClaude Opus 5 acd22a9b5e Count the log under any two of its fields
N1MM's statistics window. `StatisticsPivot` counts the contacts under one row
field and one column field, with the row total, the running total and the
column totals N1MM prints. The fields are N1MM's own two lists: 29 down the
rows, 20 of them across the columns, sorted the way N1MM's SQL sorts them, so
zone 10 comes after zone 2 and 80M before 20M.

The window has N1MM's two tabs. The table is the grid with `Tot` and `Accum`
and a `Total` line; the graph is a stacked column per row, one segment per
column, in one of N1MM's twelve palettes. "Include Dupes & X-QSOs" counts the
contacts left out of the claimed score and the ones marked as dupes.

Checked against a 3775-contact CQ WW SSB log: 3762 counted, which is what
`select count(*) ... where CLAIMEDQSO = 1 and ContactType <> 'D'` returns, and
the per-band totals match band by band.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdAYHcdRktqKry7nk414TU
2026-09-04 07:47:30 +00:00
erikbrockoandClaude Opus 5 4eb8071d57 Say when the entry has two signals on the air
N1MM blocks the transmitter when a multi-operator entry is about to break
its own class. This program does not touch the transmitter: a logger that
decides for itself when the operator may not send loses a contact every
time it is wrong, and it is wrong whenever it has stale information about
the other stations — which, on a network, is whenever a laptop has just
been unplugged.

So the entry window says so instead. While this station is transmitting
and the entry is over what it is allowed, a red box goes across the top of
the window naming the station and the band. It is above the frequency and
under the menu, so nothing it covers is anything the operator needs while
he is sending, and it is big enough to read from the other side of the
room.

`TransmitRules` is what it asks:

| Entry | Allowed |
|---|---|
| one transmitter | one signal |
| two transmitters | two signals, on different bands |
| anything else | one signal per band |

Two on one band is wrong in every multi-operator class, so it is checked
whatever the category says. A station that has not said which band it is on
counts as somewhere else: a guess there puts the box in front of an
operator doing nothing wrong.

This is also the first thing that sends `XMIT`. The message was written and
nothing called it, so the transmit column of everybody's network status
window stayed empty and no station knew when another was sending. The entry
window now says when its key goes down and what it is sending.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdAYHcdRktqKry7nk414TU
2026-09-03 23:23:46 +00:00
erikbrockoandClaude Opus 5 0b92bfc6ee Fill in the rest of N1MM's network status window
Seven tabs now, in N1MM's order, and the messages behind them.

**Options** holds what there is something behind: Log QSOs at all Stations,
which decides whether the contacts the others log are kept here; the
message trace, moved off the Messages tab where N1MM does not keep it; and
Run1/Run2, which used to be guessed from the radio number and is now the
operator's choice, sent in every `STATUS` and every `XMIT`. The transmit
interlocks stay out: this program does not block a transmitter, and a
station that claimed it did would be waited on for nothing.

**Call Stack Targets.** A callsign heard at one station and handed to the
position that will work it — `STACKANOTHERCALL` with the call alone,
`STACKCALL` with the contact behind it. The target is picked from the tab
or from the station list, and what arrives goes on the call stack of the
radio the operator is on, sorted by whether it is a multiplier here.

**The master** is the station the entry follows for cluster spots and the
time. `MASTER` names it to everybody, so the column agrees everywhere.

**The clock.** `TIME` carries a station's clock and Force Time Sync sends
it. N1MM sets the clock from it when it is running as an administrator and
says how far out it is when it is not; this program always does the second,
because setting the clock needs root and a contest logger has no business
asking for it. Anything over two seconds is called out.

**Serial numbers.** Reset Serial Numbers at all Stations sends
`RESETQSONRS` and every station starts again at one. The contacts already
logged keep the numbers they were sent with.

**What the entry agrees on** is now checked rather than assumed:

| What | How |
|---|---|
| the contest | `CONTESTNAME` as soon as a connection opens, and again in every `CHECKSUM` |
| the country file | its issue date — the publisher's `=VER20260826` — rides in `STATUS` |
| the computer name | longer than N1MM's fifteen characters is named at startup; a second computer answering to this one's name is named when its beacon arrives |
| the station number | two stations on the same number are named when the second says so |

`CountryFile` reads its issue date, which nothing had asked it for before.

Left for later: the master relaying its cluster spots, and the shared
serial-number pool where stations claim numbers in advance.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdAYHcdRktqKry7nk414TU
2026-09-03 23:15:44 +00:00
erikbrockoandClaude Opus 5 b5d86c1677 Send the messages N1MM actually reads
Three of the messages this program sends were its own invention or in the
wrong units, so an N1MM beside it read them wrongly or not at all.

| Message | N1MM | What was sent |
|---|---|---|
| `XMIT` | transmitting, the function key's caption, Run2, F1 pressed, stop sending | transmitting, the radio number |
| `PASSFREQ` | the frequency in hundredths of a kilohertz, and nothing else | the frequency in kilohertz, and a callsign |
| `FREQ` | the frequency in hundredths of a kilohertz | `BANDMAP`, which N1MM has no such message for |

So N1MM showed a radio number where the other operators read what a
station is sending, read a pass on 14205.5 as 140.25, and never saw this
station move at all. `BANDMAP` is gone: the frequency goes out in `FREQ`
when the radio moves and the mode, run and the rest ride in the `STATUS`
that follows it, which is where N1MM carries them.

The `Msg` column now shows what a station is sending, which is what the
caption is for. `PassCall` and `RadioNumber` are gone from the station:
neither is on the wire, so neither was ever anything but this program
talking to itself.

**A station that reconnects resynchronises by itself.** N1MM sends
`LASTQAT` — when I last worked anybody — as soon as a connection opens,
and a station holding nothing that late asks for the hours in between. An
empty log asks for nothing: a program that has just been started has no
business pulling the whole contest across, and N1MM suppresses it the same
way. The answer goes over the connection this station opens, which is not
always up when the message arrives, so a resync that has nothing to ask
over waits for it rather than being lost — and one that still cannot send
stops and says so instead of leaving the row resynchronising for the rest
of the contest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdAYHcdRktqKry7nk414TU
2026-09-03 22:54:16 +00:00
erikbrockoandClaude Opus 5 2a21c772ad Resynchronize a log with the other stations, and rescore it
A station that was away — a laptop that lost the network, one that was
switched off — comes back missing whatever the others logged while it was
gone. N1MM fills that in by walking the log an hour at a time, and this is
that walk, on the same messages:

| Message | What it says |
|---|---|
| `CHECKSUM` | here is what I hold for your contacts in this hour, and the contest and callsign I am logging |
| `DELETEQS` | drop your copies of my contacts in that hour, I am sending them again |
| `RESYNCQSO` | one of those contacts |
| `CONFIRMED` | here is what the hour hashes to at my end — which also asks for the next hour |

Each station is the authority on the contacts it made, so an hour that
disagrees is replaced rather than merged. The answer to one block asks for
the next, and the walk ends when the block starts after the end of the
period. The Resynchronize tab counts the hours that agreed, the hours that
were sent again and the hours that still disagreed, which are N1MM's
columns.

**`LogChecksum`** is N1MM's: a CRC32 over the `DXLOG` columns of the block,
ordered by time and then callsign, leaving out what each station works out
for itself — points, the three multiplier flags, the country prefix, the
continent, the WPX prefix and `IsOriginal`. Two stations may disagree about
those and still hold the same log. A checksum that disagrees for any other
reason costs one block of traffic, which is what a resync is for.

**The link still holds no log.** It asks one through `ContactLog` for the
contacts of a computer in a block of time, and `SessionContactLog` answers
on the window thread, because that is where the log is touched.

**Every contact logged here now carries the computer that made it.** N1MM
stamps `NetBiosName` and `NetworkedCompNr` on every row and this program
did not, so its own contacts belonged to no station and a resync could not
ask for them.

**Rescore** writes the points and multiplier flags of a period back to the
database. The log in memory scores itself whenever it changes; the stored
rows do not, and N1MM reads them rather than working them out. A contact
that arrives from another station after the one that took a multiplier
leaves those columns behind. A station can also ask the others to rescore
when its resync finishes, which is the flag on the end of `CHECKSUM`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdAYHcdRktqKry7nk414TU
2026-09-03 21:52:09 +00:00
erikbrockoandClaude Opus 5 68b29d722a Give the network status window N1MM's tabs and columns
The window was one flat table of columns this program made up. N1MM's has
tabs and a fixed set of columns, and an operator who knows one has to be
able to read the other.

**The window** now has Stations, Actions and Messages. Stations is a grid
with N1MM's headers in N1MM's order — Computer, IP Address, Pass, Run,
10, 100, Freq, Op, Msg, Send, Receive, Band, R/M, Mode — and a row per
station, this computer included. Right-click a row to pass the contact
being worked to that station, to talk, to show the connection log, or to
close and open its connection. Actions holds the talk line, the pass
frequency, the echo and the networking switch; Messages holds the chat,
the connection failures, the echo round trips and a trace of every
message that arrives.

**`STATUS`** is what fills those columns. N1MM sends it every few seconds
whether anything has changed or not — the pass frequency, the frequency,
run or search and pounce, the operator, the rates over the last ten and
the last hundred contacts, the mode, the categories and the version — so
a station sitting on one frequency for an hour still has a current row.
`BANDMAP` only says a station moved. The five transmit-interlock flags
are sent as false: this program does not block a transmitter.

**`Rates.PerHour`** reads the two rate columns out of the log. Count back
over the last N contacts, stop at a gap longer than the contest's minimum
off time, and treat a span under a minute as a minute, so three contacts
in five seconds read as 180 an hour rather than 2160.

A connection closed by hand stays closed. Without that flag the next
beacon opens it again within ten seconds, which makes the menu item look
broken.

Left out, because there is nothing behind them: N1MM's Resynchronize and
Rescore tabs, the transmit interlocks of its Options tab, the Master
column and the call-stacking target tab.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdAYHcdRktqKry7nk414TU
2026-09-03 21:39:42 +00:00
erikbrockoandClaude Opus 5 6f2f39768c Show the other computers, and apply the ten-minute rule
Three pieces on top of the link:

**The network status window**, on the entry window's Window menu. A row
per station, this computer included the way N1MM shows it: number,
address, operator, band, mode, run, transmit, pass frequency, the last
message type, how long ago it arrived, the counts each way and the echo
round trip. A station that broadcast the wrong version is named under the
table in red — that is the one fault where everything looks connected
and nothing arrives. The box at the bottom sends a line of chat, and
Echo asks every station whether it is there.

**Where this station is** goes out once a second when it has changed.
The frequency, the mode and run have a dozen places they can change from
— the radio moving, a band button, a QSY typed into the callsign box —
so it is read and compared rather than announced from each of them.

**The ten-minute rule.** `BandChangeRules` already counted changes and
the stay on a band, and the entry window already showed the countdown,
but only a user-defined contest carried a rule, so every built-in
contest allowed anything. `ForCategory` gives a multi-operator entry
with one or two transmitters a ten-minute stay, which is N1MM's fallback
in `ContestInstance.BandChangeTimerDuration` for every contest that does
not name its own. Its per-contest table is a few hundred cases in a
decompiled hash switch and is not repeated.

Config ▸ Edit Networked-Computer Names now covers both networks: the
12060 broadcast to other programs and the 12070 link, with the station
number, the port, the version to claim and stations named by address for
a network where a broadcast does not reach.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdAYHcdRktqKry7nk414TU
2026-09-03 15:26:42 +00:00
erikbrockoandClaude Opus 5 9f635550bd Read a QTC series out on RTTY
N1MM's Send All: the whole series as one message — the heading, every
line with the operator's spacing between them, then the ending, with
`{TX}` and `{RX}` around it so it keys the transmitter and drops it
again. The shape is N1MM's, from `QTCWindow.cs:3609`. A line is the three
fields joined with hyphens, which is how N1MM writes them, and Snd n
sends one line again for a station that missed it.

One message rather than a button per line because a series is close to a
minute of transmission at 45 baud: the type-ahead buffer holds it and
paces the engine.

Three settings with N1MM's names and defaults — `WAESendAllHeadingText`,
`WAESendAllEndingText` and `WAESQTCSpacing` — on the QTC window's setup
dialog. `{ENTERLF}` is new to the expander and stands for a carriage
return and a line feed; `{QTC}` is filled in by the window, which is the
only place that knows which series is going out.

The messages are tested as text. Nothing has gone on the air.

Left out: N1MM's four SSB recordings, which need a voice keyer, and its
RTTY messages for the station taking traffic down, which are typed into
the digital window's transmit pane here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdAYHcdRktqKry7nk414TU
2026-09-03 15:02:08 +00:00
erikbrockoandClaude Opus 5 0fc8d7e405 Write down what starts the transmit pane again
Two things do, not one: the engine reporting the drop, and a new message
being keyed. The second was missing from the text, which is the fault the
last commit fixed.

The claim that the bridge does not forward MMTTY's window message 32772
is dropped. `docs/digital-bridge.md` argues the control cannot raise
`OnTranslateMessage` for a message it has an event for, `OnPttEvent` does
report drops here, and the two files disagreed. Says what is known and
what is not instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdAYHcdRktqKry7nk414TU
2026-09-03 14:46:37 +00:00
erikbrockoandClaude Opus 5 f49a8c10fd Start the transmit pane again on every new message
The macro buttons went wrong at the end of a transmission, both faults in
DigitalEngineSender:

- The engine reports the transmitter drop on its own thread.
  WhenTransmitChanged took the state lock, decided the message had ended,
  released the lock, and only then called Buffer.Ended(), which clears
  what has gone to the engine. A macro pressed on the last character got
  through StartAsync in that gap and had already flushed its own text into
  Sent, so Ended() wiped the new text off the pane while the engine
  transmitted it. Ended() is now called inside the same lock.

- The pane only started again when the engine reported a drop. Two macros
  in a row keep the transmitter up, so that report never came and Sent
  grew with every press. Everything in Sent is locked, because it is in
  the engine and cannot be taken back, so the whole pane became
  read-only. TypeAhead.Started() drops the last message's sent text and
  keeps what was typed ahead, and StartAsync calls it whenever it keys a
  new transmission.

The rest of this commit is the digital transmit work these fixes sit on:
the pane as one coloured box, the sender's three keying states, the
type-ahead feeder paced by the clock with the engine's count as a brake,
{RX} flushing what is left in one piece, and the entry window's function
keys reading the digital macros on a digital mode.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdAYHcdRktqKry7nk414TU
2026-09-03 14:40:30 +00:00
erikbrockoandClaude Opus 5 7ae60be4a0 Write down what the count lag and the symbol count measure
The third probe run puts numbers on both. TxBufLen lags by 100 to 150 ms, about
one character at 45.45 baud: twenty-one characters pushed at 2496 ms read 0 at
2497, 2547 and 2597, then 26 at 2647. It moves once per symbol transmitted, so
readings 50 ms apart repeat.

It read 26 for 21 characters because it counts Baudot symbols, and the message
carried two digits: a shift to figures and a shift back each time, plus one at
the start. That is the case the cap was needed for and I had not seen. A contest
exchange is mostly digits, so the engine always has more to transmit than the
clock thinks, and the clock alone would run ahead of it on every QSO.

Slack of three characters covers both, so nothing changes in the pump. The
count reaching 0 does not cut the end of a message off either: it emptied 350 ms
before the last character was decoded back and the engine held the transmitter
up for another 900 ms.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtspmWmS7f8kUvcyaHpRWZ
2026-09-01 23:20:11 +00:00
erikbrockoandClaude Opus 5 5370ad3f6d Do not trust the engine's count as the pace
The second probe run read TxBufLen as 0 one millisecond and again fifty
milliseconds after twenty-one characters had been pushed, while those characters
were already going out: the first of them was decoded back off the air 420 ms
later. The number is right when it is a second old and wrong when it is fresh,
so it runs behind the engine.

A pump that fed on it would have handed over the whole message in half a second
and put all of it beyond reach, which is worse than the clock it replaced. So
the clock is the pace again, and the count is a check on it: the engine is never
given more than Lead + Slack characters however fast the clock says to feed, and
a message ends when the count and the clock estimate both say it has. Characters
fed since the last answer are added to it, so a stale answer cannot be spent
twice.

The engine is set to Character out: ABCD with no space after it went out at
once. Word out would have held it, so the holding case stays, but it now needs
the count to be neither going down nor being added to, since an engine kept
exactly at the cap has a count that does not move either.

The probe writes down every reading for two seconds after a push and does not
believe an empty one in that window, which is what the run needed to measure the
lag and did not do.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtspmWmS7f8kUvcyaHpRWZ
2026-09-01 23:13:28 +00:00
erikbrockoandClaude Opus 5 4118cd6d33 Pace the pump on what MMTTY says it has left
The engine probe answered two questions on the air, and they point opposite
ways.

TxBufLen is the number of characters left to transmit. Seventeen characters
pushed, five decoded back, and it read 12, counting down to 0 as the message
went out. So the pump no longer counts character times off the clock: it asks
the engine, feeds while the answer is under Lead, and asks again. The baud rate
is now only the fallback for an engine that will not answer.

A backspace is not an edit. Pushed in as a character it made the count go up by
four and the text went out unchanged, so text the engine has been given cannot
be taken back, only aborted. EngineTypeAhead was built on the opposite belief
and is gone, along with the setting that chose it and the interface that existed
to switch between the two.

A count that stops going down means the engine is holding what it has: MMTTY set
to Word out keeps a word until the space after it. The pump feeds one character
per look while that lasts, so the space arrives and the word goes out rather
than the message sitting there.

The probe kept two bugs of its own that this run showed: it read the count a
millisecond after pushing, saw 0 and called the message finished, and it started
a step while the previous unkey was still in flight, so that step ran with the
engine down. It now needs two empty answers in a row and waits for the engine to
say it has stopped. It also asks what {RX} does to a word the engine is holding,
which decides whether a macro without a trailing space loses its last word.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtspmWmS7f8kUvcyaHpRWZ
2026-09-01 23:02:00 +00:00
erikbrockoandClaude Opus 5 917a05b898 Key MMTTY the way N1MM does, with the control's PTT property
Nothing was ever transmitted. SetMmttyPTT does not start a transmission: N1MM
calls it with 1 to stop once the buffer is empty, which is its XmitOff, and with
0 to stop now, which is its AbortXmit. A transmission starts by setting the
control's PTT property, in XmitOn.

So the bridge learns `key <0|1>` for that property, and MmttyEngine now keys
with it, ends a message with SetMmttyPTT(1) so the buffer still goes out, and
aborts with SetMmttyPTT(0). This is the digital {TX} in the entry window and the
digital window as well as the probe: none of them could key the engine before.

The probe checks that the engine keys before it measures anything, and stops
with a plain statement if it does not, rather than reporting numbers from an
engine sitting still. It also asks a new question: whether the engine holds a
word until the space after it, which is MMTTY's Way to send. Received characters
are marked as noise while the engine is not transmitting, since a machine with a
sound card decodes the band all the way through the run.

The first run on a real engine says MMTTY 1.70 connects, the control answers
TxBufLen and refuses NotAProperty with DISP_E_UNKNOWNNAME. What TxBufLen counts
is still open: read while nothing was transmitting it rose over time and rose by
four after four backspaces, which is not what characters-left would do.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtspmWmS7f8kUvcyaHpRWZ
2026-09-01 22:51:43 +00:00
erikbrockoandClaude Opus 5 54b9181c06 Let MMTTY hold the text waiting to go out, behind a setting
MMTTY has a type-ahead buffer of its own: characters go into it, a backspace
takes back one it has not transmitted, and TxBufLen says how many are left. Used
that way it paces itself, so there is no gap between characters to tune and no
baud rate to keep in step with the engine.

EngineTypeAhead does that, and Config > Digital picks between it and the pump
that is there now. Off is still the default: three things it rests on have never
been seen with a real engine.

tools/Nonemm.EngineProbe asks the engine those three questions and writes the
answers to a file. It starts MMTTY through the bridge, pushes a message, polls
TxBufLen while it goes out, backspaces over text that has and has not been
transmitted, and logs what came back on the receive side and when.

The bridge learns one verb for it: `buffer` reads TxBufLen and answers with the
count, or -1 when the control will not say. A property the control does not know
is a log line rather than an error, since it stops nothing.

TypeAhead and EngineTypeAhead share the TransmitBuffer interface, which is what
the digital window now works through, so the window does not know which one it
has.

docs/unfinished.md states what each buffer assumes and how to run the probe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtspmWmS7f8kUvcyaHpRWZ
2026-09-01 22:41:18 +00:00
erikbrockoandClaude Opus 5 da7f7fb3f5 Keep the engine fed instead of feeding it a character at a time
The pump handed the engine one character and then waited a character time
before the next, on purpose, so it stayed behind the engine rather than ahead.
That leaves the engine with an empty buffer between every two characters of a
message, and an engine with an empty buffer transmits idle rather than waiting.
The idle is added to how long the message takes, which in a contest is time
paid for nothing.

The pump now keeps two characters in the engine: one being transmitted and one
behind it, so the engine never runs dry. The clock at the baud rate says when
the engine has room for the next one, and the engine reporting that it has
stopped transmitting sets the estimate back to an empty buffer.

The last two characters are now beyond reach rather than the last one. Everything
before them can still be rewritten, which is what the pane is for.

A message is finished when the buffer is empty and the engine is estimated to
have transmitted what it holds, so {END} and {RX} no longer run while the engine
still has the last characters of the message.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtspmWmS7f8kUvcyaHpRWZ
2026-09-01 21:56:41 +00:00
erikbrockoandClaude Opus 5 9d0af06686 Say how the transmit pane works and what would make it exact
The pump is paced off the clock. XMMT.ocx has a TxBufLen property and answers
when it is asked, so the bridge could report what the engine still holds and
make the red text exact, but the engine does not start on this machine and the
property could not be watched while a message was going out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-09-01 05:25:49 +00:00
erikbrockoandClaude Opus 5 0e2aee545e Say what the digital window still does not have
Sending a text file is in, so it comes off the list. AutoTRXUpdate goes on it:
the title bar carries the offset frequency now, but the entry window and the
log still record the dial frequency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-09-01 00:03:35 +00:00
erikbrockoandClaude Opus 5 654adda080 Keep an operator's windows, colours and recordings
Ctrl+O asks who is at the radio, as it does in N1MM, and the callsign goes on
every contact logged from then on. A multi-operator station changes hands every
few hours, so each operator also keeps what he wants the program to look like:
where the windows sit, the theme, and the folder his recordings are in.

The windows on the screen are stored against the operator leaving, before the
dialog opens, and the operator taking over gets his own back. A window he opens
later is placed as it appears. An operator who is new to the list starts with
whatever is on the screen.

Nothing plays the recordings yet, so the folder is stored and waits on voice
keying. {OPERATOR} in a message sends the callsign.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-31 22:28:18 +00:00
erikbrockoandClaude Opus 5 c8de74e24a Add the digital interface, running MMTTY under Wine
MMTTY and 2Tone have no socket or pipe interface: N1MM hosts XMMT.ocx and
exchanges window messages with the engine. A Linux process cannot load that
control, so bridge/nonemm-mmtty-bridge.exe hosts it under Wine and passes
lines over its standard input and output. docs/digital-bridge.md states the
protocol and what the control needs.

The window is N1MM's: receive pane with coloured callsigns, grab list, call
stacking, twenty-four macro buttons and the engine controls. One left click
copies what is under it — a callsign to the callsign box, anything else to the
exchange box the contest keeps for that kind of value.

Config > Digital registers XMMT.ocx in the Wine prefix on its own, making the
prefix first if it is not there. The engine, the bridge and the control start
at the copies shipped beside the program.

The bridge reads the control's own events. OnTranslateMessage carries only the
messages the control has no event for, so nothing was ever decoded through it.

hamlib's data mode names read as digital modes now, and a mode typed into the
callsign box changes mode the way a frequency changes band, so a station with
no radio can reach RTTY at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-31 21:35:45 +00:00
erikbrockoandClaude Opus 5 76b57cee1d Carry out the call history section directives
A call history file is built from years of logs, so its section and
state columns carry old names, and the file says at the top what to do
about that. The four lines that say it were read and passed over.

All four are now carried out, in N1MM's order: !!MapOnSection!! stores
the old Ontario sections as ON, !!ValidateArrlSection!! drops a section
that is not one of N1MM's, !!GTA2GH_NT2TER!! stores GTA as GH and NT as
TER, and !!Validate50State!! keeps a state, turns a section into the
state it is in, or drops the value.

The section list is the one N1MM's call history reader validates
against, which is not the list its entry window offers: it holds the
retired GTA and NT, and leaves out ON. It sits beside the reader in
Nonemm.Core rather than with the contest lists, which are the entry
window's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-31 14:21:02 +00:00
erikbrockoandClaude Opus 5 2eeda451c3 Count down the band change timer in the status bar
SOBandChangeTimerDuration and MOBandChangeTimerDuration say how long a
station has to stay on a band it has just moved to. Both were read by
nothing: N1MM counts them down in its info window, which this program
does not have.

The countdown now stands in the entry window's status bar, next to the
contacts and multipliers, ticking with the clock that is already there.
BandChangeRules.StayLeft works it out from the log, starting the stay at
the minute after the contact that changed band, which is where N1MM
starts it.

Which contests show it is decided at runtime rather than by a list: the
rules come from the contest for the entry's own category, so a file that
asks for no stay, or asks for one only of single-op entries while the
station is entered multi-op, shows nothing at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-31 13:51:17 +00:00
erikbrockoandClaude Opus 5 de010fb981 Say that the country-dependent exchanges are written
docs/unfinished.md said REF, ARRL 10M, the Ukrainian DX contest and the
Russian DX contest were not written yet. All four are in
src/Nonemm.Contests/Rules, and the first three implement SkipsField for
the country-dependent exchange the sentence is about.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-31 13:32:12 +00:00
erikbrockoandClaude Opus 5 6c2ee0f78f Act on the .udc session, off-time and band-change settings
Four settings were read by nothing.

MultipleSessions = 0000/30 runs the contest in half-hour sessions from
00:00 UTC. The session is part of the dupe key, so a station worked in
an earlier one may be worked again, and ResetMultsEverySession and
ResetSNEverySession start the multipliers and the serial numbers over.

DupeQSOMinutesAgo is the other way back to a station: after so many
minutes rather than in the next session. The log holds the time of the
last contact per dupe key instead of only the key, and the setting's
IgnoreBand and ThisMode decide the scope, because a file that uses it
turns DupeType off.

MinimumOffTime drives the time on and time off the score summary now
shows under the score, and beside them are the band changes made against
the allowance for a contest that limits them. N1MM's countdown for the
minutes you have to stay on a new band needs its info window and is not
here.

UdcFile.Flag now takes 1 and 0 as well as True and False, which is what
these settings are written as and what N1MM reads.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-31 13:32:01 +00:00
erikbrockoandClaude Opus 5 e3fa979584 Read the .udc CallHist multiplier and BonusPoints2
MultSqlString = CallHist counts every station the call history file
lists, once per callsign, which is how N1MM dedups it. The file reaches
the scoring code through ContestLog.History, and a QsoContext now
carries it; without a file loaded nothing counts.

BonusPoints2 = +50, calls.txt reads the callsigns from the support-files
folder and adds 50 to the contact, *2 doubles it and a plain 50 scores
50 instead. The folder comes from the registry, which the app passes
when it reads the .udc files. N1MM's other form, where the file is
grids.txt and the bonus is looked up by grid square, is not read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-31 13:31:25 +00:00
erikbrockoandClaude Opus 5 1d3167cd04 Check a .udc section multiplier against the list the file names
MultWindowType names the list N1MM validates a received section against,
and every exchange that was not all digits counted as a multiplier here.
Four of N1MM's lists are held now -- the ARRL sections, the US states,
the Canadian provinces and the 165 OK/OM districts, which came out of
N1MM's own exe -- and a file naming one of them counts only the values
on it. A file naming any of the hundred-odd others keeps the old rule.

The 2023 OK/OM DX log now scores contact for contact as N1MM did. Two
contacts in the 2026 log still differ: the district code is stored with
a trailing space, which N1MM matches against its list untrimmed, so it
counts no multiplier where this counts one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-31 13:30:27 +00:00
erikbrockoandClaude Opus 5 921033cd93 Carry alternating CQ on after a contact
Alternating CQ ran off every message the keyer finished, so working a
station that answered started a CQ on the other radio, and Escape --
which stopped it outright -- was the way around that. It now follows
N1MM: only the CQ message carries the alternation to the other radio,
anything else holds it where it is, and so does Escape. The next CQ
picks it up again, which is what the CQ after a logged contact does, so
Ctrl+B is needed once rather than after every contact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-31 13:29:44 +00:00
erikbrockoandClaude Opus 5 12dab9644a Log the time the BARTG exchange went out
The BARTG RTTY contest sends the time with the serial number, and both
stations log the time the other one sent. The entry window now has the
report, serial and time boxes N1MM's has, and the time this station sent
is stamped into the MiscText column as the exchange message goes out, or
at log time when nothing sent it. The Cabrillo line writes that time and
falls back to the time of the contact when the column is empty, which is
what N1MM writes, in N1MM's columns.

The total score was points times every multiplier. N1MM's class works
out points times the countries and areas together, times the continents.
Per-contact points and multiplier flags do not change, so the station's
2022 log still scores contact for contact as N1MM did.

N1MM has a class each for the March HF RTTY contest and the September
sprint. This program has one contest holding the March rules under the
sprint's name, and docs/unfinished.md now says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-31 13:29:08 +00:00
erikbrockoandClaude Opus 5 a07b181ee1 Write the numbered Cabrillo lines a .udc file asks for
CabrilloFormat 2, 3, 4 and 5 name the NAQP, NA Sprint, Sweepstakes and
section-and-serial lines. A file asking for one of those got the default
line; it now gets the layout, ported from CabrilloString2 and
CabrilloString4 in N1MM's Contact.cs, in N1MM's columns.

The exchange the layouts split into columns is built from the boxes the
file defines, in the order it defines them. N1MM builds it from the
section box alone, which writes only half of a two-box exchange such as
NAQP's name and state.

Format 6, the ARRL RTTY Roundup line, is still not read. No published
.udc file asks for it, because the Roundup has a contest class of its
own.

ExchangeSlots.ValueOf moves to Nonemm.Contests as QsoExchange.ValueOf,
so the contest code can read a contact through its exchange boxes
without a second copy of that switch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-31 12:27:49 +00:00
erikbrockoandClaude Opus 5 cf5bdd6082 Lay out a Cabrillo line the way a .udc file asks for it
`CabrilloString` names a field and a column width in turn. The line it makes
follows N1MM: the operator's callsign in the first 13 columns, then every
column padded to its width with one space after it, and a value longer than its
column cut rather than pushed sideways. The cut is new for every contest, not
just the user-defined ones — the columns are the sponsor's, not ours.

`CabrilloVersion` is read too, so a contest whose sponsor still asks for 2.0
gets it in the header.

A contest that lays out its own line can put the transmitter column where it
wants it, or leave it out, so the writer no longer adds one at the end.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-31 10:38:39 +00:00
erikbrockoandClaude Opus 5 5ab97251e1 Say what happens instead of making files do things
A file is written to; it does not write, score or name anything. Five comments
and doc lines said otherwise, two of them older than today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-31 10:13:25 +00:00
erikbrockoandClaude Opus 5 38e9010802 Let the published .udc files score the contests they are written for
I wrote SA 10 and OK/OM DX SSB in code after two failed attempts at the
download. The download works — the page posts a nonce with the form — and the
files say I got both contests wrong.

SA 10 is deleted from the code. Its file scores the station's log to the
contact, where the code had the wrong Cabrillo name and counted its multipliers
once in the contest rather than once per mode.

OK/OM DX now covers the CW running only, which is what N1MM's class is for and
what N1MM's own documentation says: the SSB running has different rules and a
pair of .udc files, one for each side. The CW rules go back to the class, so
Czechia and Slovakia are one side of the contest again. With the SSB files in
place both of the station's SSB logs score to the contact.

Reading those files turned up four things in the .udc reader: a file whose
extension is upper case was passed over, `Country` was not read as a
multiplier source, `OtherCountry` was not a points condition, and a section
multiplier counted the serial numbers the other side of a contest sends.

All Asian stays in code — N1MM has a class for it after all — with the Cabrillo
name and the band table it uses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-31 10:00:21 +00:00
erikbrockoandClaude Opus 5 264036f0f2 Add the twelve contests left in the station's log
REF, the Ukrainian DX contest, the BARTG Sprint, ARRL 10M, Russian DX RTTY,
RDAC, YO DX HF, Oceania DX, IARU Region 1 Field Day, SARTG RTTY, All Asian and
SA 10. Every contest name in the log now opens, and every one of these but
Field Day scores its log contact for contact as N1MM did.

Most are written from N1MM's own class and checked against the log. Three
things the log settled that the class does not say plainly: ARRL 10M counts its
multipliers once per mode rather than once, the BARTG Sprint counts a continent
once in the contest while its countries and call areas count per band, and RDAC
counts nothing but the districts and scores nothing for a station that sends
none.

All Asian and SA 10 have no class in N1MM, which logs them from a .udc file, so
those two are written from the published rules and the log. docs/unfinished.md
lists what is still not settled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-31 09:43:03 +00:00
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