JTTY: A possible WSJT mode for fast RTTY-like contest exchanges

JTTY aims for an operational feel similar to conventional amateur RTTY,
with much better weak-signal performance and a much lower error rate.
Transmissions may start at any time and normally last a few seconds.

FRAME AND WAVEFORM

Each frame carries 32 message-grammar bits, a reserved-zero bit, and a
universal end-of-message bit. A 12-bit CRC extends the payload to 46 bits.
A tail-biting rate-1/2 convolutional code with constraint length K=10
produces 92 coded bits.

JTTY uses Gaussian-smoothed four-tone frequency-shift keying (4-GFSK).
A frame has 13 synchronization symbols followed by 46 coded symbols.
At 12000/384 = 31.25 baud, one frame lasts 1.888 seconds. Nominal
half-power bandwidth is 125 Hz.

The receiver verifies FEC, CRC, the universal reserved-zero bit, and the
complete source grammar. An invalid source word is discarded before it can
be displayed, mark EOM, create a receive slot, or participate in signal
subtraction.

SOURCE CONTRACTS

Ordinary keyboard text, sjtty input, externally queued strings, and untagged
N1MM/MMTTY text use the literal source interface. After uppercase and space
normalization and replacement of unsupported characters with '#', automatic
packing selects the minimum-frame combination of recognized compact atoms
and TEXT5 that preserves the normalized text exactly. Explicit RTTY Roundup
also applies serial normalization below before packing. "Literal" identifies
the text interface, not a requirement to use TEXT5 or bypass that normalization.

The eight shipped JTTY function-key templates are explicit native actions.
Their call, profile-exchange, and grid placeholders compile to typed atoms. A
customized template that does not match a native form uses automatic text
packing after normal placeholder expansion. If a recognized native template
has invalid runtime data,
transmission is rejected instead of changing meaning through a literal
fallback.

Earlier JTTY packing minimized frames using compact calls, a generic "599 "
plus five-character format, and TEXT5. STRUCT30 replaced that exchange format
with richer typed atoms. Restricting ordinary input to TEXT5 avoided guessing
whether "05" meant a serial, zone, or check, but also lost unambiguous call
compaction. Automatic packing retains the minimum-frame algorithm with
context-free recognition and an explicit exchange profile that can normalize
serials and enable additional typed candidates.

AUTOMATIC TEXT PACKING

At complete token boundaries the packer considers:

  * The six call forms below, with an exact standard-call codec round trip.
  * Registered CONTROL phrases, including inside longer messages.
  * Canonical unsigned decimal 0-131071 as GENERIC_NUMERIC, field-only or
    preceded by "599 ". Leading zeros are not canonical except for "0".
  * "599 " plus a canonical two- or three-character base-36 token containing
    at least one letter, as GENERIC_QTH. The wire length rules apply.
  * Valid four-character Maidenhead GRID4, field-only or preceded by "599 ".
  * CLASS_SECTION: count 1-32, class A-F, registered ARRL/RAC section.

Every candidate must pass codec pack/unpack validation and render exactly as
its span in the normalized text. At submission the GUI captures Unknown,
Field Day, or RTTY
Roundup from the existing special operating activity. No activity means
Unknown even though native macros default to serial exchanges. All profiles
retain the context-free candidates above; Field Day adds no further candidates.

Only RTTY Roundup adds full "599 <number>" SERIAL and full "599 <location>"
STATE_PROVINCE candidates under the native exchange rules. Locations must
contain a letter. Exact rendering after profile normalization remains
mandatory; bare serials are never
inferred. Zone, check, age, power, license-year, other specific location,
zone/location, and serial/time meanings are not inferred. No new configuration
or wire format is needed. Native actions retain their typed exchange semantics.

Before packing, explicit RTTY Roundup canonicalizes a decimal token following
a complete "599" token as a serial: "599 05" becomes "599 005", and
"599 0123" becomes "599 123". Tokens must contain one to six digits and have
value 0-131071. Unsupported tokens retain their spelling for normal fallback.
Bare digits and Unknown or Field Day input retain their spelling after ordinary
normalization. The canonical message is returned to the GUI for display and
logging. Expansion beyond 80 characters is rejected rather than truncated.

A dynamic program over source-character offsets chooses the minimum number
of frames under this recognition policy, rather than a greedy sequence.
TEXT5 consumes exactly five characters except at the end. Interior padding
cannot be used to reach a compact candidate because it would change the text.
Structured candidates consume one source space after their rendered span
when another token follows, matching the receiver's implicit separator.
Equal-cost choices prefer a structured atom, then the longest consumed span,
then ascending atom kind/subtype/role.
The subtype ordering prefers typed serial and state/province candidates over
generic candidates of equal frame count and span under RTTY Roundup.

This minimum need not equal the theoretical minimum with perfect knowledge
of contest semantics. Examples for ordinary text under Unknown or Field Day,
without a native action:

--------------------------------------------------------------------------------
Text                              Frames  Encoding or limitation
--------------------------------------------------------------------------------
CQ K1ABC CQ                           1    CQ call
CQ KA1ABC CQ                          1    CQ call
WB9XYZ                               1    Call
WB9XYZ TU CQ KA1ABC CQ                2    Two call atoms
WB9XYZ 599 123                        2    Call plus generic number
599 123                              1    Generic numeric exchange
599 MA                               1    Generic QTH exchange
599 FN42                             1    Full-role GRID4
1D EMA                               1    Class/section pair
599 001                              2    Leading zeros must be preserved
599 05                               2    No numeric kind is inferred
599 BRUCE                            2    No generic five-character exchange
--------------------------------------------------------------------------------

Under RTTY Roundup, "599 001" takes one frame and "K1ABC 599 001" takes two.
"599 123" and "599 MA" use SERIAL and STATE_PROVINCE instead of generic atoms
without changing frame counts. "599 05" and "599 0123" become "599 005" and
"599 123", each in one frame.

sjtty defaults to Unknown. Its optional leading argument
--exchange-profile=unknown|field-day|rtty-roundup applies to both the
one-message packing invocation and the eight-argument waveform invocation.
Invalid values are errors. Example:

    sjtty --exchange-profile=rtty-roundup "599 001"

SOURCE GRAMMAR

--------------------------------------------------------------------------------
i2.n2 Example                         Bits             Purpose
--------------------------------------------------------------------------------
0.0   CQ K1ABC CQ                    call28 + n2       CQ call action
0.1   K1ABC                          call28 + n2       CALL action
0.2   TU K1ABC CQ                    call28 + n2       TU/CQ action
0.3   K1ABC TU                       call28 + n2       CALL/TU action
1.0   K1ABC AGN?                     call28 + n2       CALL/AGN action
1.1   TU NOW K1ABC                   call28 + n2       TU NOW/CALL action
1.2   (reserved; invalid)
1.3   (reserved; invalid)
2     599 123                        body27 + family3  STRUCT30
3     HELLO                          6 6 6 6 6         TEXT5
--------------------------------------------------------------------------------

Bits 1-30 contain the form listed above and bits 31-32 contain i2. Bit 33
is always zero. Bit 34 is one only on the final atom of a source message.

STRUCT30 replaces the former i2=2 "599 " plus five-character form:

--------------------------------------------------------------------------------
Family Name            Body fields, in transmitted bit order
--------------------------------------------------------------------------------
000    EXCH_NUM        role1 kind4 value17 zero5
001    EXCH_LOC        role1 kind4 length1 token16 zero5
010    EXCH_PAIR       schema3 pair_data23 zero1
011    EXCH_NUM_TIME   role1 serial14 minute11 zero1
100    MISC            subtype4 subtype_data23
101    PROFILED        reserved; invalid in version 1
110    VERSIONED       reserved; invalid in version 1
111    INVALID         source-validity guard space
--------------------------------------------------------------------------------

EXCH_NUM kinds 0-7 are serial, CQ zone, ITU zone, age, power, check,
first-license year, and generic numeric. EXCH_LOC kinds 0-4 are
state/province, ARRL/RAC section, country/prefix, generic QTH, and local
administrative code. EXCH_PAIR schemas are ZONE_LOC3 and CLASS_SECTION.
MISC subtypes are CONTROL and GRID4. All other IDs are invalid.

Full-role fields render with a "599 " prefix; field-only fields omit it.
Serials use at least three digits, zones and checks at least two, UTC time
exactly four, and first-license year exactly four. Structured atoms supply
one implicit trailing space; TEXT5 characters are appended verbatim. Final
trailing spaces and TEXT5 padding are not displayed. Formatting choices and
visible repetitions are not encoded in typed fields.

NATIVE FUNCTION KEYS

--------------------------------------------------------------------------------
Key Template                    Native atoms
--------------------------------------------------------------------------------
F1  CQ %M CQ                CQ(my_call)
F2  %H %E                   CALL(his_call), configured full exchange
F3  %H TU CQ %M CQ          CALL_TU(his_call), CQ(my_call)
F4  %M                      CALL(my_call)
F5  %H                      CALL(his_call)
F6  TU NOW %Q %E            TU_NOW_CALL(queue_call), configured exchange
F7  %H AGN?                 CALL_AGN(his_call)
F8  %E                      configured full exchange
--------------------------------------------------------------------------------

Current templates and control phrases match case-insensitively while ignoring
whitespace differences, before placeholder expansion. Superseded serial
templates match only in their exact saved-default form. Keyboard F1-F8
shortcuts and clickable buttons select the same actions. EOM is set only on
the final native atom; queued native messages do not require a TEXT5 spacing
frame between them.

%E uses the selected profile: decimal serial by default, exact
"<count><class> <section>" for FIELD_DAY, and either decimal serial or a
canonical two- or three-character state/province for RTTY. %G is field-only
GRID4; the exact "599 %G" template selects full role. Exact old F2, F6, and F8
defaults using 599 %N remain recognized during migration and are replaced only
when unchanged in saved settings; edited variants remain literal.

N1MM can opt into native encoding with a leading [[JTTY:<ACTION>]] marker in
TXTEXT. Actions are CQ, CALL_EXCH, CALL_TU_CQ, MYCALL, HISCALL, TU_NOW_EXCH,
CALL_MY, CALL_TU_MY, EXCH, GRID, and CONTROL. Untagged N1MM strings use the
literal source interface with automatic text packing.
Invalid or unknown tagged requests are rejected, not transmitted as bracket
text. See jtty_source_encoding.txt and jtty_n1mm_integration.md for the payload
grammar.

The practical native transmit subset is Call8, serial and RTTY state/province,
Field Day class/section, GRID4, and registered controls. Automatic text packing
also generates GENERIC_NUMERIC and GENERIC_QTH. Other normative atoms have
canonical receive decoding and rendering but are not inferred from text or
exposed as general-purpose macros in this version.

The complete normative source definition, field ranges, enum assignments,
validation rules, and binary/hex vectors are in jtty_source_encoding.txt.

COMPATIBILITY

The old i2=2 form is intentionally incompatible with STRUCT30. There is no
version discriminator: old receivers display new STRUCT30 bits as "599" text,
and some old type-2 frames are valid new STRUCT30 words with different
meanings. JTTY is unreleased, so no legacy decoder mode is retained. Automatic
packing uses only the current grammar; unrecognized "599 ..." text remains
available through TEXT5.
