Draft specification · findings
MOSAIC protocol whitepaper
Asynchronous multi-user amateur digital mode for HF and UHF
Abstract
MOSAIC is an asynchronous multi-user packet modem family. Its design goal is a shared channel in which several uncoordinated transmitters can overlap in time and frequency while a single receiver recovers every CRC-valid packet it can prove. The current implementation has two band profiles:
- MOSAIC-HF, a 50 bit/s narrowband profile intended for an SSB-width channel.
- MOSAIC-UHF, an 800 bit/s profile for 70 cm operation where bandwidth is available.
The receiver is deliberately conservative: it only cancels a signal after the ordinary packet decoder returns a CRC-valid payload. That CRC gate is the safety mechanism that lets the receiver subtract decoded users without turning uncertain detections into destructive interference cancellation.
This document separates measured-on-hardware
evidence from software simulation. Hardware results are
sourced primarily from JSON manifests under run-artifacts/;
simulated results are labelled as such.
Evidence basis
Quantitative protocol constants in this paper come from:
src/mosaic_hf/config.pysrc/mosaic_hf/profiles.pysrc/mosaic_hf/waveform.pysrc/mosaic_hf/framing.pysrc/mosaic_hf/acquisition.pysrc/mosaic_hf/sic.pysrc/mosaic_hf/physical_bench.pydocs/RF-BENCH-HANDOFF.md- JSON manifests and radio logs under
run-artifacts/
No number in the results tables is intended as an estimate. If a result has not yet been measured, it is identified as unmeasured.
Motivation and design goals
The protocol is built for uncoordinated packet users rather than scheduled point-to-point links. The important design goals are:
- Asynchronous access. Transmitters do not need a common time reference.
- User separation by known signatures. Each candidate identity has a declared access signature; the receiver searches a catalogue of possible senders rather than accepting an unlimited blind active set.
- CRC-gated cancellation. A packet must pass the normal decode and CRC checks before it can be subtracted from the residual.
- Bounded computation. Carrier, drift, timing and cancellation searches are finite grids.
- Hardware realism. Strong RF energy is not treated as success; retained runs must prove burst duration and CRC validity.
The scaling goal remains larger than the present hardware evidence,
but the current artifacts now include genuinely independent MOSAIC-UHF
on-air results: a bladeRF x115 and a USRP B210 transmitted from separate
processes while a HackRF One decoded both packets, and retained runs
recovered twelve logical stations carried across those two independent
RF transmit chains with both spread and randomly drawn arrivals. Earlier
no-fade software scaling sweeps that appeared to show collapse were
invalid: the unfaded branch in
tools/many_station_scaling.py emitted double-sideband AM
mirror images instead of true frequency offsets. With the
analytic-signal fix, retained twelve-station no-fade seeds decode 12/12
and a corrected balanced sweep decodes 8/8, 10/10, 12/12 and 16/16. In
the clean no-fade, no-AWGN conditions tested so far, multi-user
interference is not the binding constraint. Fading is: at eight UHF
stations, current faded simulations recover only 12/32 with 6 dB
amplitude spread and 15/32 with balanced levels. The current unfaded
single-seed deep sweep recovers 22/24 and 26/32, showing graceful
degradation rather than collapse; those two large-count points are not
outage rates.
The live station has also now completed a bidirectional keyboard QSO over the antenna path at 431.200 MHz. VE6SLP and VA6GA each delivered operator text at the remote console through the KISS live modem. This corrects the earlier bench conclusion that antenna-path CRC recovery was blocked; the retained diagnosis shows the antenna path was working and the failures were in live-modem capture, scheduling, filtering and console plumbing.
The HF profile has a different evidence status. It is proven as a
one-packet waveform through real audio converters and simulated ITU-R
F.1487 channels, but its shared-channel operation is not proven in a
fading HF channel. Current hf-audio simulations show that
the receiver scales in a static channel and then collapses to one or two
recovered stations under ITU-style fading because SIC residuals are not
tracked through a time-varying packet.
Waveform and modulation
The transmit chain is fixed by the wire format: payload bytes are protected, encoded, interleaved, preceded by the signature-derived preamble, mapped onto the 16-of-192 tone grid, and shaped into a burst.

MOSAIC uses a deterministic 16-candidate tone selection on a 192-bin frequency resource grid. Each transmitted coded symbol carries 4 bits by selecting one of 16 local candidates. The candidate set changes each symbol interval as a function of the 32-bit access signature, an LCG state and a 32-bit mixer.
The mapping in candidate_bin_mapping() is defined only
for:
| Field | Value | Source |
|---|---|---|
| Candidates per symbol | 16 | src/mosaic_hf/waveform.py |
| Resource bins | 192 | src/mosaic_hf/waveform.py |
| Bins per candidate stratum | 12 | src/mosaic_hf/waveform.py |
| Access signature width | 32 bits | src/mosaic_hf/waveform.py |

The figure shows two access signatures over twenty symbol intervals. Each signature redraws sixteen candidate bins from the same 192-bin resource grid; a transmitted 4-bit symbol selects one candidate from that interval's set.

The left panel is one symbol interval from
candidate_bin_mapping(): sixteen candidate bins for
signatures 0x2F and 0x5B, with one active tone
marked. The right panel is the equal-energy 16-ary MFSK symbol map for
signature 0x2F—the modem never superposes tones inside a
symbol; the four coded bits index that interval's candidate list. Unit
envelope at the analytic component is a waveform property, not an
emissions proof.
The modem generates a unit-envelope analytic CPFSK/hopped-MFSK
waveform. Tone frequencies are selected from the profile's tone grid and
repeated for one symbol duration. The orthogonality condition is
enforced in PhyConfig:
tone_spacing_hz * symbol_duration_s == 1
That constraint is why MOSAIC-UHF changes both symbol duration and tone spacing together. Keeping 192 bins and 16 candidates preserves the multi-user resource structure while changing only the time and frequency scale.
Framing, FEC and CRC
The implemented P1 packet format is in
src/mosaic_hf/framing.py.
| Item | Value | Source |
|---|---|---|
| Maximum PHY payload | 255 bytes | MAX_PAYLOAD_BYTES |
| Header data | 4 bytes | HEADER_DATA_BYTES |
| Header information including CRC | 5 bytes | HEADER_INFO_BYTES |
| Header CRC | CRC-8/ATM, poly 0x07, init
0 |
crc8_atm() |
| Payload CRC | CRC-16/CCITT-FALSE, poly
0x1021, init 0xffff |
crc16_ccitt_false() |
| FEC | terminated convolutional code, K=7 | CONSTRAINT_LENGTH |
| Generators | octal 171,
133 |
_GENERATORS |
| Tail bits | 6 | TAIL_BITS |
| Interleaver | 4 rows, transmit by column | interleave() |


The protected header carries magic 0xD3, version,
payload length and reserved semantics, then CRC-8/ATM over those five
information bytes. Header and payload regions are independently
terminated with a K=7 rate-1/2 convolutional code (generators octal 171,
133), 4-row column-read interleaved, and packed four bits per 16-ary
tone symbol. The decoder recovers the protected header first, uses the
header length to size the payload coded block, and accepts the payload
only when CRC-16/CCITT-FALSE passes. Only that full CRC-valid path may
enter cancellation.
framing.py also contains an experimental M1/P4-A
protected-header candidate. It is not described here as the active wire
format because encode_frame() and the retained RF manifests
use the P1 path.
Acquisition
The acquisition preamble contains 32 known symbols derived from the
access signature. Symbol i uses:
mix32(signature + (i + 1) * 0x9e3779b9) & 0x0f
The reference AcquisitionConfig searches:
| Field | Reference default | Source |
|---|---|---|
| Preamble symbols | 32 | AcquisitionConfig |
| CFO range | -112.5 to +112.5 Hz | AcquisitionConfig |
| Coarse CFO step | 12.5 Hz | AcquisitionConfig |
| Drift hypotheses | -3, 0, +3 Hz/s | AcquisitionConfig |
| Timing step | 8 samples | AcquisitionConfig |
| Fine CFO step | 0.5 Hz | AcquisitionConfig |
| Detection threshold | 2.4 | AcquisitionConfig |
Retained SDR captures required a wider explicit physical search. The band profiles therefore use a -2000 to +2000 Hz acquisition range for hardware runs. MOSAIC-HF searches drift hypotheses of -10, 0 and +10 Hz/s. MOSAIC-UHF uses 5 ms symbols, a 160 ms preamble, a 200 Hz coarse CFO step, an 8 Hz fine CFO step and only a 0 Hz/s drift hypothesis; the source notes that 10 Hz/s drift would move less than 2 Hz over that preamble.
The multi-user receiver
src/mosaic_hf/sic.py implements bounded offline
catalogue discovery and successive interference cancellation (SIC). A
candidate is not an assertion that a transmitter is active; it is one
catalogue identity and access signature. On each residual generation,
the receiver:
- Acquires every eligible catalogue entry on the residual.
- Ranks detected hypotheses by measured acquisition score.
- Attempts ordinary packet decoding.
- Marks rejected candidates by stage.
- Cancels at most CRC-valid packets.
- Repeats for a bounded number of generations or until no CRC-valid candidate remains.


The cancellation ledger retains the prior residual segment, start and
end samples, fitted gain and phase, and the acquisition-anchored
trajectory fit. The code reports false_cancellation_count
as structurally zero on the manager side because ledger entries are
CRC-gated; campaigns with generated truth can separately check whether a
ledger identity was inactive.
The flowchart is the safety argument in one page: the original
mixture r₀ is immutable; each generation searches a finite
catalogue; ordinary FEC/CRC decode must pass before a reconstruction is
fitted; only then does
r_{k+1} = r_k - a_k s_k(θ_k)
update the residual, with a LIFO undo snapshot on the ledger. A failed CRC never writes the residual. This is not a joint maximum-likelihood receiver and it is not an unlimited blind active-set estimator. Its safety comes from the CRC gate and from bounded catalogue search.
Operator fast path and residual peel. The live modem
(tools/mosaic_modem.py) first walks each catalogue
signature independently. That recovers equal-power overlapping stations
far faster than a full SIC campaign and is what keeps continuous receive
on the RF stream. Finding some stations must not stop the search for the
rest: incomplete multi-user recovery continues with a CRC-gated residual
peel (cancel_crc_valid_packet) and, when still incomplete,
the full collision/SIC receiver. Payloads from each stage are merged by
identity.
Gain tracking on cancellation. The cancellation fit
uses a piecewise-linear complex gain over a hat-function time basis when
the profile enables it (PhyConfig.gain_knot_symbols;
MOSAIC-HF defaults to three symbols per knot). A constant-gain fit is
always computed; tracking is accepted when residual power improves by a
clear margin, or when a mild BIC penalty on long extents still prefers
the tracked model. That is the right estimator structure for HF, but
alpha still does not claim shared-channel success under
ionospheric fading: measured multi-user fade recovery remains limited,
and further diversity / tracking work remains open.
Mathematical specification
The tone grid is orthogonal over one symbol. For tone spacing
Δf and symbol duration T_s, the implemented
profiles require:
Δf T_s = 1
∫_0^{T_s} exp(j 2π (m-n) Δf t) dt = 0, m != n
MOSAIC-UHF is therefore not a separate modulation. It scales
MOSAIC-HF by shortening T_s and increasing Δf
together while keeping 16 candidates and 192 resource bins.
The AWGN channel helper defines SNR against the profile's reference
bandwidth B_ref, not against total discrete-time Nyquist
bandwidth. The implemented variance is:
σ² = P_signal / 10^(SNR_dB/10) * (f_s / 2) / B_ref
This definition is the basis for simulated SNR tables. The relationship to energy per bit is:
Eb/N0 = SNR_ref * B_ref / R_b
Eb/N0_dB = SNR_ref_dB + 10 log10(B_ref / R_b)
For MOSAIC-HF, B_ref/R_b = 2400/50 = 48, so the
narrowband HF profile has 16.8 dB of processing gain relative to its
2400 Hz SNR reference bandwidth:
Eb/N0 = SNR_ref · (B_ref / R_b)
Eb/N0_dB = SNR_ref_dB + 10·log10(B_ref / R_b)
MOSAIC-HF: B_ref = 2400 Hz, R_b = 50 bit/s → Eb/N0_dB = SNR_2400Hz_dB + 16.8 dB
MOSAIC-UHF: B_ref = 38400 Hz, R_b = 800 bit/s → Eb/N0_dB = SNR_38400Hz_dB + 16.8 dB
For MOSAIC-UHF, B_ref/R_b = 38400/800 = 48, so the same
conversion applies. This conversion is mandatory when comparing MOSAIC
results with other HF modes: -9 dB SNR in the 2400 Hz reference band is
7.8 dB Eb/N0, not an operation point near -9 dB Eb/N0. Example MOSAIC-HF
conversions are:
| SNR in 2400 Hz | Eb/N0 |
|---|---|
| -9 dB | 7.8 dB |
| -12 dB | 4.8 dB |
| -15 dB | 1.8 dB |
| -18 dB | -1.2 dB |
| -21 dB | -4.2 dB |
| -24 dB | -7.2 dB |
This processing gain is the central HF trade: the 50 bit/s profile spends an SSB-width channel to buy about 17 dB of energy-per-bit margin. It is also why the two profiles can share the same matched-noise Eb/N0 cliff while occupying different bandwidths.
SIC subtracts only decoded, CRC-valid packets. At cancellation
generation k:
r_{k+1} = r_k - a_k s_k(θ_k)
Here r_k is the residual, s_k(θ_k) is the
reconstructed waveform at the acquired timing/CFO/drift trajectory, and
a_k is the fitted complex amplitude (gain and phase). If
the header or payload CRC fails, this update is not applied.
The Watterson-style fading helper applies delayed, independently faded analytic paths and normalizes path powers as a group:
y[n] = Σ_p sqrt(P_p) h_p[n] x_a[n - d_p]
h_p[n] = (1/sqrt(M)) Σ_{m=1..M} exp(j(2π f_{p,m} n/f_s + φ_{p,m}))
Σ_p P_p = 1
x_a is analytic audio, d_p is an integer
sample delay, each Doppler tone f_{p,m} is drawn within the
path's Doppler support, and the current reference slice does not
implement fractional-delay filters.
Band profiles
The profile abstraction in src/mosaic_hf/profiles.py
binds PHY parameters, TX shaping, direct-IQ filtering and acquisition
settings so that rate-dependent stages move together.
| Parameter | MOSAIC-HF | MOSAIC-UHF | Source |
|---|---|---|---|
| Audio sample rate | 12,000 Hz | 96,000 Hz | profiles.py |
| Symbol duration | 80 ms | 5 ms | config.py, profiles.py |
| Tone spacing | 12.5 Hz | 200 Hz | config.py, profiles.py |
| Candidates / bins | 16 / 192 | 16 / 192 | config.py, profiles.py |
| Lowest tone | 300 Hz | 300 Hz | config.py, profiles.py |
| Highest tone | 2687.5 Hz | 38,500 Hz | config.py, profiles.py |
| SNR reference bandwidth | 2,400 Hz | 38,400 Hz | config.py, profiles.py |
| Nominal SSB bandwidth | 2,800 Hz | 40,000 Hz | config.py, profiles.py |
| Raw coded bit rate | 50 bit/s | 800 bit/s | profiles.py |
| Receive FIR taps | 129 | 1025 | profiles.py |
| Receive transition | 100 Hz | 3000 Hz | profiles.py |
| TX shaping taps | 241 | 1921 | tx_shaping.py, profiles.py |
The UHF profile is the same waveform family scaled by 16 in rate. The trade is bandwidth for time. The handoff notes report a software matched-noise sweep in which both profiles have the same cliff at about +1 dB Eb/N0. The same notes also state that, at fixed transmit power, UHF collects 12 dB more noise in its wider bandwidth. Therefore UHF is not a weaker waveform, but it does trade link margin for shorter air time.
Implementation on real radios
Hardware and transports
Implemented tools and retained bench runs use:
| Radio | Role in retained runs | Format | Source |
|---|---|---|---|
| bladeRF x115 | forward transmit, reverse receive | SC16 Q11 for vendor CLI | docs/RF-BENCH-HANDOFF.md,
physical_bench.py |
| HackRF One | forward receive, reverse transmit | CS8 for hackrf_transfer |
docs/RF-BENCH-HANDOFF.md,
physical_bench.py |
| USRP B210 | independent UHF transmitter and UHF decoding observer in retained runs | interleaved complex64 | tools/usrp_io.py,
run-artifacts/three-radio-uhf-2/usrp-tx.log,
run-artifacts/usrp-observer-2tx-hi/usrp-rx.log |
The direct-IQ path also uses CF32LE/SigMF files for retained captures and observer products.
The B210 path is intentionally a separate process like the bladeRF
and HackRF vendor tools. tools/usrp_io.py runs under
/usr/bin/python3, because the UHD Python bindings are
installed system-wide, and exchanges interleaved complex64 sample files.
Its default UHD selector is type=b200. That default is not
cosmetic: UHD also exposes the bladeRF through its SoapySDR bridge, so
empty device arguments can silently open the wrong radio. The tool
records positive identity fields (mboard_id,
mboard_serial) so a retained result proves what actually
transmitted or received. In
run-artifacts/three-radio-uhf-2/usrp-tx.log the opened
transmitter identifies as mboard_id B210,
serial 31B92DD, with TX gain 52.0 dB, 1,000,000 sample/s
rate and 1.29 s transmit duration. In
run-artifacts/usrp-observer-2tx-hi/usrp-rx.log, the same
B210 identifies as serial 31B92DD while receiving at gain
70.0 dB and 2,000,000 sample/s.
MOSAIC-HF access through a transceiver audio path
The bench still cannot make a two-radio HF RF link directly. The
handoff now records measured tuning limits: the bladeRF x115 stops at
237.5 MHz and rejects set frequency rx 14100000 with
Invalid parameter; it would need an XB-200 transverter that
is not on the bench. The B210 tunes down to 42 MHz, lower than its
published 70 MHz figure but still above HF. The HackRF reaches HF, but
it is half duplex and therefore cannot be both ends of a two-radio link.
That makes a direct two-radio HF RF link physically impossible on this
bench.
That does not block MOSAIC-HF, because HF digital modes normally
reach the band through an SSB transceiver's audio path, not by asking a
computer interface to be an HF transmitter. MOSAIC-HF is already an
audio-band waveform: the profile opens a 200-2800 Hz audio band around
tones from 300 Hz to 2687.5 Hz, and its 12 kHz modem sample rate is
exactly one quarter of the 48 kHz rate available on ordinary sound
interfaces. src/mosaic_hf/audio_path.py and
tools/mosaic_hf_audio.py provide that missing path: audio
out to the rig's microphone input, audio back from the receiver, and
optional serial RTS/DTR PTT.
Three audio-path design choices are protocol-relevant because they prevent known HF-digital failure modes:
- Drive is capped, not normalized.
AudioPathConfig.transmit_amplitudedefaults to 0.25. Hot audio drives an SSB transmitter into ALC compression and splatters into neighbouring channels. - Bursts are padded. The default guard is 300 ms of lead silence and 200 ms of tail silence, giving PTT relays and ALC time to settle before the preamble.
- PTT is explicit.
PttControlkeys serial RTS or DTR. Leaving PTT unset permits VOX, but VOX is the fallback because it keys on the first audio it hears and can clip the burst start.
The rate conversion itself also has a measured failure mode. The 12
kHz to 48 kHz conversion uses a polyphase filter with group delay;
resampling a burst with no trailing room can truncate final symbols and
make the decoder fail with
stream ends before coded payload.
tests/test_audio_path.py now prepares the burst exactly as
it is transmitted, including the keying guard, before checking
rate-conversion decode.
The retained analogue-converter result is not a software loopback. The handoff records a Focusrite Scarlett 4i4 as device 13, with a physical output-to-input path on channel 0: a 1500 Hz probe returned 46 dB SNR, while channel 1 was silent. A full MOSAIC-HF packet was sent through the real DAC, analogue wiring and real ADC with:
python tools/mosaic_hf_audio.py --amplitude 0.9 loopback --device 13 \
--repeat 3 --text "VE6SLP-1 DE VA6GA-2 QSL 599 K"All 3 of 3 attempts decoded byte-exact, with acquisition scores from 647 to 677 and received RMS around 0.009. The return is about 40 dB below the transmitted level, which is why that bench probe used amplitude 0.9. On a real SSB transceiver, drive should start at the default 0.25 and be raised only until ALC just begins to move. This result demonstrates the converter path and the modem's tolerance of it; it is not an HF on-air contact and does not measure ionospheric propagation.
Direct-radiated MOSAIC-HF waveform on a UHF SDR carrier: acquisition only
run-artifacts/hf-rf-link/offline-decode.json records a
partial measured RF result for the MOSAIC-HF profile radiated directly
by an SDR over the real antenna path at 431.200 MHz. This did not open
an HF band; it carried the HF waveform on the already-authorized UHF
bench carrier. The bladeRF x115 transmitted with txvga2 10
dB and the pinned FPGA
7bb0c7b0fb9976cd631b679352aff3383ab371dad29306452140984363711fdf
at version 0.16.0; the USRP B210 received at 52 dB. One packet was
transmitted. The retained offline decode is tied to capture SHA-256
3d691092ca4379de901e3683bd8e38d962a07f8f91de2c2abc12ba662ad19875
and 187,000,000 capture samples.
The result is a clean partial: the receiver acquired the burst and correctly ranked the true sender first, but no CRC-valid packet was recovered.
| Candidate | Acquisition score | CFO | Drift | Rank | Rejection |
|---|---|---|---|---|---|
VE6SLP-1 |
1314.2 | +106.25 Hz | -1.0 Hz/s | 1 | header |
VA6GA-2 |
158.9 | +160.75 Hz | +10.0 Hz/s | 2 | header |
crc_valid_sender_ids is empty. Both candidates evaluated
963 coarse and 546 fine hypotheses. The true sender's acquisition score
is about 8.3 times the other catalogue entry, so the burst was radiated,
propagated, detected and attributed, but it was not decoded. An
independent FFT measurement on the same capture placed the carrier at
+107.8 Hz, agreeing with the receiver's +106.25 Hz estimate to within 2
Hz; that corroborates acquisition and points the fault downstream.
The leading explanation is drift, not a proven root cause. MOSAIC-HF uses 12.5 Hz tone spacing, the reciprocal of its 80 ms symbol, and the packet is about 13 s long. The estimated -1.0 Hz/s drift accumulates to about 13 Hz over the frame, more than one full tone spacing. The preamble can still correlate before that drift dominates, while later header tones can walk off their bins. The current coarse drift grid of -10, 0 and +10 Hz/s also poorly covers a true drift near -1 Hz/s.
This partial does not contradict the intended MOSAIC-HF deployment. A 12.5 Hz tone grid presumes a more stable reference than a free-running SDR local oscillator at 431 MHz. That assumption is exactly what the transceiver audio path provides: the rig performs RF translation from its own reference and the modem sees audio. The same profile decodes byte-exact through real analogue converters and has the simulated ITU-R F.1487 threshold bracket described below. MOSAIC-UHF, with 200 Hz tone spacing, continues to be the profile used for live keyboard QSOs over this UHF antenna path.
Emission window
--frequency-hz sets the local oscillator. The modem
audio is placed 100,000 Hz above that LO. For
--frequency-hz 431200000, the emitted occupied spectrum
is:
| Profile | Emitted spectrum | Source |
|---|---|---|
| MOSAIC-HF | 431.300200-431.302800 MHz | emission_window_hz(),
docs/RF-BENCH-HANDOFF.md |
| MOSAIC-UHF | 431.300200-431.338700 MHz | emission_window_hz(),
docs/RF-BENCH-HANDOFF.md |
An observer tuned to 431.200 MHz is not listening to the MOSAIC tones; for the HF profile, the observer should tune the lower edge as USB audio.
FPGA pinning hazard
The bladeRF hazard is severe enough to be part of the protocol
evidence model. The handoff records that libbladeRF silently auto-loaded
FPGA v0.14.0 after a reconnect. That image accepted
set samplerate tx 1000000 and reported the requested rate,
while actually clocking the DAC 4 times too fast. The failed burst was
strong but undecodable: a 17.14 s waveform radiated in 4.278 s, with
0.02 s symbol dwell instead of 0.08 s and about 20 kHz occupied
bandwidth instead of about 2.4 kHz.
The fix is to load hostedx115-latest.rbf in the same
bladeRF-cli process that streams samples.
BladeRfHackRfConfig expects FPGA version 0.16.0 and SHA-256
7bb0c7b0fb9976cd631b679352aff3383ab371dad29306452140984363711fdf.
The live modem refuses --execute without an FPGA
bitstream.
This is why received signal strength alone never proves a valid transmission. Retained manifests compare measured burst duration to expected waveform duration and require CRC validity.
The B210 selector failure is the same class of bench hazard. In both cases the bench accepted a plausible command while using hardware other than the operator intended: a stale bladeRF FPGA in one case, and a UHD-opened bladeRF instead of a B210 in the other. Both are controlled by recording positive radio identity, not by trusting that a command completed.
Methodological guardrails
Two bench details now have explicit artifact support because either can make a false result look plausible:
- Envelope profiles. The three-radio manifests carry
an
envelope_profile, a sequence of 100 ms receive-level buckets. It exists because "a station did not transmit", "a station transmitted at the wrong time" and "a station was present but too weak" can all decode as an absent packet. The envelope is the field that distinguishes those cases after the fact. - Per-band fading delay spread.
tools/many_station_scaling.pynow defaults the simulated delay spread by profile: 0.002 s for HF and 0.000001 s for UHF. The source notes that a 2 ms spread is ionospheric, while at 70 cm the spread is microseconds and therefore sub-sample for this model.
The same section of the tool also documents the bladeRF keying issue:
--bladerf-lead-seconds defaults to 1.05 s because the
bladeRF CLI loads the FPGA image before it can stream. The lead
compensates the otherwise late bladeRF burst when another process, such
as /usr/bin/python3 tools/usrp_io.py, is scheduled by wall
clock.
The tool also refuses to let one radio observe and transmit in the
same run. Full-precision observers are decoded with
decode_collision_iq() in
src/mosaic_hf/physical_bench.py; this keeps B210 CF32
captures in their native precision instead of first quantizing them to
the HackRF's 8-bit CS8 format.
tools/decode_capture.py exists for the same
evidence-preservation reason. A congested decode can take longer than
the capture itself; if the decode process is interrupted after the
radios have finished, re-keying the transmitters would create a
different channel realization. The tool decodes the retained capture
offline, records the capture SHA-256 in its JSON output, and accepts
--format cf32 for full-precision observers as well as CS8
HackRF recordings.
The general rule for negative results is now explicit: check the
received level before attributing a failed decode to the receiver. The
low-gain B210 observer run at
run-artifacts/usrp-observer-2tx/ detected both candidate
preambles but recovered no CRC-valid payloads; its 100 ms envelope
buckets peak at 0.011240958236157894. The high-gain rerun at
run-artifacts/usrp-observer-2tx-hi/ peaks at
0.0802842453122139 and recovers both packets. The decode result alone
cannot distinguish an under-gained front end from a receiver limitation;
the envelope profile can.
The same lesson applies to population simulations. Superseded scaling
files first suggested the wrong near/far mechanism, then seemed to show
congestion collapse, and a coordinated-arrival explanation was offered
when hardware decoded 12/12. Those explanations are withdrawn for
current scaling claims: some old artifacts used a defective unfaded
generator that manufactured mirror-image interference. The fault was in
the sweep tool's signal generator, not in the receiver or protocol; the
receiver did not become better, the measurement harness stopped lying
about the channel. The first HF multi-user harness had the same failure
mode: multiplying real audio by a cosine is amplitude modulation, not a
frequency shift, and leaves a mirrored image that no catalogue entry can
cancel. The correct path uses the Hilbert analytic signal before
applying carrier offsets, as the channel code already did. Files without
the even_spacing provenance key are kept as historical
records, not current performance evidence. The fifth methodological
lesson is the blunt one: when hardware beats the model, suspect the
model.
The live QSO adds the same lesson at system scale. Individual
transmitter, receiver, decoder and console pieces passed in isolation
while the assembled station failed. Progress came from measuring inside
the running station: archiving real receive windows with
--save-captures, timing actual decode workers, printing
candidate offsets and repeating A/B tests rather than trusting one
observation. Rejection taxonomy is useful, but not absolute: header
rejection, payload_fec_crc and strong acquisition followed
by failure can vary from run to run on a marginal link.
The first five live-station defects are now fixed or guarded by tests:
- Split bursts. Back-to-back fixed receive windows
could cut a randomly timed UHF burst across a capture boundary.
OverlappedCapturecarries a 2 s tail forward and suppresses duplicates by candidate end position. - Duplicate suppression used mismatched units.
Candidate start and end offsets are reported in the receiver's decimated
units, while the overlap was counted in front-end IQ samples.
decode_window()now compares by fraction of the window, avoiding the bug that made every decoded candidate look like a duplicate in the carried tail. - Decode workers oversubscribed the host.
MOSAIC_ACQUISITION_THREADS=1does not constrain NumPy's BLAS pool._worker_cpu_budget()usesthreadpool_limits(1)and appliesos.nice(10)once per worker process; the module-level guard matters becauseos.nice()is relative. - The station decoded silence.
window_energy_ratio()compares the loudest 0.05 s slice to the median slice. The handoff records silence at 1.00-1.02, bursts at 36.6, and a 1.15 threshold that kept 4 of 50 real windows and skipped 46 without mismatches. - Console listeners could exit immediately.
tools/mosaic_console.pytreats--listen-secondsas a complete bounded listen session even without--once, so a background listener no longer falls through to an interactive prompt and exits on stdin EOF.
A subsequent usability pass added two measurement-driven changes:
- The station decoded too much silence around
traffic. The same loud-slice measurement now crops a receive
window from the first loud slice to the last, with 0.5 s of guard on
both sides. The span deliberately includes all loud slices, so
overlapping stations stay in one decode. On
run-artifacts/solo-usrp-observer/raw_rx.cf32, the crop is 1,200,000 to 5,400,000 of 16,000,000 IQ samples and preserves the byte-identical payload. The carried overlap is rebased by subtracting the crop start. - USRP receive setup cost ate the duty cycle. UHD rebuilds its streamer for every capture, costing about 2.0 s of deaf time per window on the B210. The USRP default receive window is now 16 s so that fixed cost is amortized; this became affordable only after decode cost followed the cropped traffic span.
Three more operational defects were found in that pass, bringing the running total to eight:
- Receive failures could busy-spin. A stale capture
process holding the B210 made every receive fail instantly, burying the
real error.
ModemStationbacks receive failures off exponentially up to one receive window. - SIGTERM could orphan a radio subprocess.
tools/mosaic_modem.pynow turns SIGTERM into the same interrupt path as operator Ctrl-C, allowing normal teardown instead of leavingusrp_io.pyholding the B210. - Send-and-exit console sessions could transmit nothing or
close too soon.
tools/mosaic_console.py --oncenow accepts piped stdin as transmit lines and lingers briefly aftersendall()so the modem can read the frame.
tests/test_live_modem_radios.py guards the live-radio
fixes, including the overlap carry, unit-converted duplicate
suppression, energy-ratio squelch, traffic cropping, concurrent-burst
crop span and overlap rebasing.
One proposed FPGA-related live-modem "fix" is explicitly retracted.
The source shows bladerf_transmit_command() already falls
back from its fpga_bitstream argument to
config.fpga_bitstream, so changing the caller was not the
decisive antenna-path fix. FPGA pinning remains required, but that
particular change was a no-op for the live QSO failure.
The HF channel-validation work added another process lesson. A sweep
run with an outer ProcessPoolExecutor at 24-32 workers
oversubscribed the host because
src/mosaic_hf/acquisition.py already uses its own
acquisition ThreadPoolExecutor. The documented control is
MOSAIC_ACQUISITION_THREADS; when an outer process pool owns
the cores it must be set to 1. The handoff records 10 OS threads per
trial process before the fix and 2 after setting
MOSAIC_ACQUISITION_THREADS=1. BLAS environment variables
did not help because the extra threads were the modem acquisition pool,
not BLAS. The transferable lesson is to measure the thread count instead
of assuming an environment variable took effect.
The first ITU-R F.1487 sweep also produced a false negative before
the harness was corrected. It passed a bare encoded burst to the decoder
with no trailing samples, so decoding could run out of buffer with
stream ends before coded payload for reasons unrelated to
the channel. Two clues prevented that from becoming a protocol claim:
flat-channel results were nearly independent of SNR, and total failure
on a 0.5 ms / 0.1 Hz channel was implausible against an 80 ms symbol.
Padding the receive buffer fixed the flat and good spot checks. The same
class of defect had already appeared in audio-path resampling, where
polyphase filter group delay required trailing room. A result that is
flat against a parameter it should depend on is treated as evidence of a
harness defect, not as proof of a robust modem.
Transmitter cleanliness and ambient RF
Measured-on-hardware transmitter and site observations:
| Observation | Result | Evidence |
|---|---|---|
| Uncalibrated TX LO leakage, carrier to wanted signal | -56.3424566419605 dB | run-artifacts/leakage-baseline/carrier-leakage-manifest.json |
| Generated waveform DC content | -220 dB | docs/RF-BENCH-HANDOFF.md |
| TX DC calibration result | -16.203982223509964 dB carrier to signal | run-artifacts/leakage-calibrated/carrier-leakage-manifest.json |
Effect of calibrate dc tx |
about 40 dB worse | docs/RF-BENCH-HANDOFF.md, leakage manifests |
| Ambient carrier at 431.200 MHz with HackRF streaming | 15.128914018928498 dB above local floor | run-artifacts/rx-lo-leakage-1/receiver-lo-leakage-manifest.json |
| Ambient carrier at 431.200 MHz with HackRF idle | 16.046696200057355 dB above local floor | run-artifacts/rx-lo-leakage-1/receiver-lo-leakage-manifest.json |
The DC calibration must not be used for the measured setup. The ambient carrier is unrelated to the modem and is present with all bench transmitters idle.
Measured results
Cabled hardware results
These are measured-on-hardware results over retained cabled captures.
Rows whose profile is HF are MOSAIC-HF profile tests; they
are not HF-band RF links.
| Artifact | Profile | Direction | Result | CFO / drift | Burst | Evidence |
|---|---|---|---|---|---|---|
run-artifacts/vendor-bladerf-tx-hackrf-rx-20260803/ |
HF | bladeRF -> HackRF | CRC-valid A -> B, text x |
+1186.0 Hz / 0.0 Hz/s | not in manifest | vendor-run-manifest.json |
run-artifacts/vendor-hackrf-tx-bladerf-rx-20260803/ |
HF | HackRF -> bladeRF | CRC-valid B -> A, text x |
-1210.0 Hz / -1.0 Hz/s | not in manifest | vendor-run-manifest.json |
run-artifacts/vendor-bladerf-tx-hackrf-rx-full-20260803/ |
HF | bladeRF -> HackRF | CRC-valid MOSAIC-A -> MOSAIC-B, text
hello-node |
+1289.0 Hz / 0.0 Hz/s | not in manifest | vendor-run-manifest.json |
run-artifacts/cabled-431200000-ve6slp-20260804/ |
HF | bladeRF -> HackRF | CRC-valid | +1494.75 Hz / 0.0 Hz/s | not in manifest | vendor-run-manifest.json |
run-artifacts/uhf-cabled-1/ |
UHF | bladeRF -> HackRF | CRC-valid | +468.0 Hz / 0.0 Hz/s | 1.29 / 1.31 s, complete | vendor-run-manifest.json |
run-artifacts/uhf-cabled-reverse-1/ |
UHF | HackRF -> bladeRF | CRC-valid | -484.0 Hz / 0.0 Hz/s | 1.149 / 1.17 s, complete | vendor-run-manifest.json |
The 50 dB cabled safety facts record bladeRF TX conducted power of
0.0 dBm (1.0 mW) at the TX connector and a 50 dB fixed 50-ohm pad in the
cabled path
(run-artifacts/measured-50db-cabled-facts-20260803.json).
On-air single-packet hardware results
All retained on-air results use bladeRF txvga2 of 10 dB
or less when the bladeRF is the on-air transmitter, and the handoff
states that the radios were a few feet apart. Rows whose profile is
HF are MOSAIC-HF profile tests over the bench's UHF antenna
path, not ionospheric HF contacts.

The waterfall is measured on-air data from
run-artifacts/solo-usrp-observer/raw_rx.cf32: a USRP B210
observing one MOSAIC-UHF packet with access signature 47 over the
antenna path at 431.200 MHz. The plotted slice is representative of the
retained 128 MB complex64 capture; the spectrum panel shows the
corresponding transmitted profile containment. The weaker mirrored
structure is not a second MOSAIC station; it is already present in the
retained bladeRF SC16 transmit stream as a residual transmit-side image
about 62 dB below the wanted component, and the retained decode for this
capture contains only VE6SLP.
| Artifact | Profile | Direction | Result | CFO / drift | Peak / floor | Burst | Evidence |
|---|---|---|---|---|---|---|---|
run-artifacts/on-air-431200000-fpga016-1/ |
HF | bladeRF -> HackRF | CRC-valid | +467.75 Hz / -1.0 Hz/s | peak 0.31396645307540894; floor not recorded | 17.112 / 17.14 s, complete | vendor-run-manifest.json |
run-artifacts/on-air-reverse-1/ |
HF | HackRF -> bladeRF | CRC-valid | -445.25 Hz / 0.0 Hz/s | peak 0.17589831352233887; floor not recorded | superseded guard reported 22.0 / 15.54 s | vendor-run-manifest.json and handoff note |
run-artifacts/on-air-txtest-20260805/ |
HF | bladeRF -> HackRF | CRC-valid | +419.25 Hz / 0.0 Hz/s | peak 0.3709171712398529; floor not recorded | 16.473 / 16.5 s, complete | vendor-run-manifest.json |
run-artifacts/on-air-txtest-20260805-reply/ |
HF | HackRF -> bladeRF | CRC-valid | -442.25 Hz / 0.0 Hz/s | peak 0.180165097117424; floor 0.07945477217435837 | 15.189 / 15.22 s, complete | vendor-run-manifest.json |
run-artifacts/uhf-on-air-1/ |
UHF | bladeRF -> HackRF | CRC-valid | +724.0 Hz / 0.0 Hz/s | peak 0.33341526985168457; floor 0.008717949502170086 | 1.29 / 1.31 s, complete | vendor-run-manifest.json |
run-artifacts/uhf-on-air-reverse-1/ |
UHF | HackRF -> bladeRF | CRC-valid | -724.0 Hz / 0.0 Hz/s | peak 0.10893066972494125; floor 0.08279542624950409 | 1.169 / 1.19 s, complete | vendor-run-manifest.json |
run-artifacts/solo-usrp/ |
UHF | USRP B210 -> HackRF | CRC-valid VE6NAS |
+692.0 Hz / 0.0 Hz/s | peak 100 ms mean magnitude 0.0833497866988182 | no burst field; envelope profile retained |
three-radio-manifest.json |
run-artifacts/solo-bladerf/ |
UHF | bladeRF -> HackRF | CRC-valid VE6SLP |
+804.0 Hz / 0.0 Hz/s | peak 100 ms mean magnitude 0.09547556936740875 | no burst field; envelope profile retained |
three-radio-manifest.json |
run-artifacts/solo-bladerf-txvga10/ |
UHF | bladeRF -> HackRF | CRC-valid VE6SLP |
+812.0 Hz / drift not recorded | peak 100 ms mean magnitude 0.30271145701408386 | no burst field; envelope profile retained |
three-radio-manifest.json |
run-artifacts/solo-usrp-observer/ |
UHF | bladeRF -> USRP B210 | CRC-valid VE6SLP |
+100.0 Hz / drift not recorded | peak 100 ms mean magnitude 0.04625697433948517 | B210 receive gain 52.0 dB | three-radio-manifest.json,
usrp-rx.log |
The reverse UHF run is a valid packet, but its peak is close to the recorded floor. The handoff describes this as not a comfortable link.
All three bench radios have now been exercised in real RF roles: bladeRF as a transmitter, HackRF as a decoding receiver, and B210 as both a transmitter and a decoding receiver. That does not make the front ends equivalent, but it does show the waveform is not receivable only by one specific SDR path.
run-artifacts/hf-rf-link/offline-decode.json is also
measured RF evidence, but it is deliberately reported as a partial: the
MOSAIC-HF waveform radiated on the UHF SDR carrier acquired strongly and
attributed VE6SLP-1 first, then failed at header decode
with no CRC-valid sender. It is not an on-air HF contact and not a
successful direct-radiated MOSAIC-HF packet.
On-air independent multi-transmitter hardware result

The left panel summarizes retained radiated UHF multi-user decodes. The centre panel is current-harness no-fade simulation (complete recovery through 16 stations; single-seed 22/24 and 26/32 beyond that) with faded eight-station means marked separately. The right panel is the HF shared-channel simulation ceiling under ITU-style fading. Artifact paths are cited in the tables below.
run-artifacts/three-radio-uhf-2/three-radio-manifest.json
is the first retained result in this repository where two physically
independent transmitters are decoded by a third radio. It is stronger
evidence than the composite runs because the transmitters do not share a
local oscillator, power amplifier, timebase, sample clock or exact
digital start time; their relative keying is only as repeatable as the
operating-system scheduler.
| Role | Radio | Station and payload | RF settings | Evidence |
|---|---|---|---|---|
| Transmitter 1 | bladeRF x115 | VE6SLP -> VA6GA,
independent alpha de VE6SLP, access signature
0x2f |
txvga1 -4 dB, txvga2 0 dB |
three-radio-manifest.json |
| Transmitter 2 | USRP B210 | VE6NAS -> VA6SLP,
independent bravo de VE6NAS, access signature
0x5b |
TX gain 52.0 dB | three-radio-manifest.json,
usrp-tx.log |
| Observer | HackRF One | receiver-only bounded SIC decode | LNA 8 dB, VGA 32 dB, 2,000,000 sample/s, 8 s capture | three-radio-manifest.json,
hackrf-rx.log |
Measured receiver result:
| Artifact | Profile | Result | CFOs | Timing in 96 ksample/s audio | Stop condition | Evidence |
|---|---|---|---|---|---|---|
run-artifacts/three-radio-uhf-2/ |
UHF | CRC-valid VE6SLP, VE6NAS |
VE6SLP +804.0 Hz; VE6NAS +700.0 Hz |
VE6SLP 2.9784166666666665-4.228416666666667 s;
VE6NAS 2.9959375-4.2659375 s |
all unique catalogue entries cancelled | three-radio-manifest.json |
run-artifacts/three-radio-uhf-4station/ |
UHF | CRC-valid VE6NAS, VA6GA,
VE6SLP, VA6SLP |
+692.0, +604.0, +796.0, +908.0 Hz | decoded extents span 2.98046875-4.610666666666667 s | all unique catalogue entries cancelled | three-radio-manifest.json |
run-artifacts/onair-4station-imbalanced/ |
UHF | CRC-valid VA6GA, VE6NAS,
VE6SLP, VA6SLP |
+1308.0, +1196.0, +796.0, +444.0 Hz | decoded extents span 3.033-4.563 s | all unique catalogue entries cancelled | three-radio-manifest.json |
run-artifacts/onair-8station/ |
UHF | CRC-valid VE6NAS-1, VE6SLP-2,
VE6SLP-1, VE6NAS-2, VA6SLP-1,
VA6GA-2, VA6SLP-2, VA6GA-1 |
-44.0, +764.0, +340.0, +956.0, +28.0, +548.0, +940.0, -300.0 Hz | decoded extents span 3.0403541666666666-4.59759375 s | all unique catalogue entries cancelled | three-radio-manifest.json |
run-artifacts/onair-12station/ |
UHF | CRC-valid VE6SLP-1, VE6SLP-2,
VE6SLP-3, VE6NAS-1, VE6NAS-2,
VE6NAS-3, VA6GA-1, VA6GA-2,
VA6GA-3, VA6SLP-1, VA6SLP-2,
VA6SLP-3 |
+404.0, +820.0, +20.0, +1012.0, +252.0, +652.0, -236.0, +604.0, +76.0, +996.0, -156.0, +428.0 Hz | decoded extents span 2.9991875-4.66775 s | all unique catalogue entries cancelled | offline-decode.json |
run-artifacts/onair-12station-random/ |
UHF | CRC-valid VE6SLP-1, VE6SLP-2,
VE6SLP-3, VE6NAS-1, VE6NAS-2,
VE6NAS-3, VA6GA-1, VA6GA-2,
VA6GA-3, VA6SLP-1, VA6SLP-2,
VA6SLP-3 |
+564.0, +460.0, +692.0, +1156.0, +1108.0, +796.0, -260.0, +996.0, +692.0, +108.0, -100.0, +1012.0 Hz | decoded extents span 3.131177083333333-4.4988125 s | all unique catalogue entries cancelled | three-radio-manifest.json |
For three-radio-uhf-2, the two decoded extents overlap
for 1.2324791666666667 s, computed from the manifest sample indices and
the MOSAIC-UHF 96,000 sample/s audio rate. The residual power trajectory
in that receiver record is 0.0012211621653254354 ->
0.0006059456766651809 -> 0.00011367312073785727 after CRC-gated
cancellation of VE6SLP and then VE6NAS.
The four-station run is a stronger receiver-load test, but not a
four-radio claim: it uses two independent RF chains, each carrying two
digitally superimposed logical stations. The bladeRF chain carries
VE6SLP and VA6SLP; the USRP chain carries
VE6NAS and VA6GA.
The imbalanced four-station run is a stronger unequal-power on-air
case. It puts three logical stations on the bladeRF and one on the B210.
Both radios' waveforms are peak-normalized to 0.9499999284744263 before
transmission, so the bladeRF composite's scale is 0.221676317396694
while the single-station B210 scale is 0.5033002154561952. In the
receiver fit, the B210-carried VA6GA gain is
0.08786596575527592; the three bladeRF-carried gains are
0.04468409135754174, 0.04516445963073307 and 0.043744873770962436,
putting VA6GA 5.780477366688979-6.057870382925946 dB above
them. This is consistent with the 7.12215636413299 dB digital-scale
difference between the B210 and bladeRF waveforms, partly offset by the
solo-calibration result that the bladeRF at txvga2 0 is
1.1797552840885095 dB hotter at the HackRF observer. All four still
decode, with peak 100 ms mean magnitude 0.11614685505628586; the run
sits comfortably inside the -10 dB no-channel near/far bound rather than
at its edge. The operational hazard is that the number of logical
stations composited on one radio silently sets their transmit level;
adding another station to a composite attenuates the others. That is a
bench artifact of the composite generator, not a protocol property.
The eight-station run uses SSID-style suffixes to make
VE6SLP-1 and VE6SLP-2, for example, distinct
station identities while both are still VE6SLP transmissions. This fixed
a bench identification limitation rather than changing the protocol:
addressed_payload() allows source and destination strings
up to 63 bytes, and the three-radio tool now accepts a source that is
either the licensed callsign or that callsign plus an SSID suffix. The
run puts four stations on the bladeRF and four on the B210, observed by
the HackRF; the manifest records bladeRF txvga2 10, B210
gain 64.0 dB, peak envelope 0.34632688760757446 and minimum envelope
bucket 0.008672110736370087. The waveform digital scales were
0.21578100857516117 on the bladeRF and 0.18644231263234207 on the B210.
The handoff records the run as an example of applying the received-level
rule predictively: the higher bladeRF and B210 gains were checked in a
prepare-only run before radiating, rather than by diagnosing a failed
on-air decode.
The twelve-station runs are now the largest retained on-air decodes.
The handoff records six logical stations composited on the bladeRF and
six on the B210, again observed by the HackRF. In
run-artifacts/onair-12station/, those twelve stations were
deliberately given even arrivals from 0.00 s to 0.66 s in 0.06 s steps
and well-separated carrier offsets. The first decode attempt was
interrupted after the radios had already finished, so the retained CS8
observer capture was decoded offline with
tools/decode_capture.py; offline-decode.json
ties the result to run-artifacts/onair-12station/raw_rx.cs8
with SHA-256
7fdb4bbad1d5f03e4f096fe3724d3fe90aa86cce8f6e5df43408bc8fcc0129f4.
It recovers all twelve catalogue identities and stops with
all unique catalogue entries cancelled.
The follow-up run-artifacts/onair-12station-random/ used
randomly drawn arrivals and carrier offsets for the same twelve
stations; the handoff records seed 4242, with the closest pair 1.0 ms
apart in arrival and 2.0 Hz apart in offset. It also recovers all twelve
stations and stops with
all unique catalogue entries cancelled. The earlier claim
that the twelve-station on-air success depended on coordinated arrival
is therefore withdrawn.
Shared-channel status by profile
The central shared-channel claim is proven on MOSAIC-UHF hardware.
run-artifacts/onair-12station-random/three-radio-manifest.json
is radiated, completed on air, uses independent bladerf and
usrp transmitters, has catalogue size 12, and contains all
twelve expected CRC-valid sender IDs: VE6SLP-1,
VE6SLP-2, VE6SLP-3, VE6NAS-1,
VE6NAS-2, VE6NAS-3, VA6GA-1,
VA6GA-2, VA6GA-3, VA6SLP-1,
VA6SLP-2 and VA6SLP-3.
run-artifacts/onair-8station/ recovers 8/8 and
run-artifacts/onair-4station-imbalanced/ recovers 4/4 on
the same radiated, two-independent-transmitter basis. The honest nuance
is physical: two radios carried twelve logical stations, so this is not
a twelve-converter claim. The two RF carriers are nevertheless genuinely
independent hardware with no shared clock or timing reference, decoded
by a third radio.
The same claim is not proven on MOSAIC-HF. Auditing
the retained multi-station artifacts shows that the on-air
shared-channel records above all carry band_profile: uhf;
HF evidence had been single-packet until the simulated
hf-audio scaling runs described below.
Live keyboard QSO over the antenna path
The handoff now records a bidirectional live keyboard QSO at 431.200 MHz between two independent stations running the live KISS modem rather than offline bench scripts:
| Console | Delivered text | Evidence |
|---|---|---|
| VE6SLP | [08:54:58Z] <<< VE6SLP DE VA6GA UR 599 IN CALGARY K |
docs/RF-BENCH-HANDOFF.md |
| VA6GA | [09:05:44Z] <<< VA6GA DE VE6SLP AGN 73 SK |
docs/RF-BENCH-HANDOFF.md |
The station roles were VE6SLP using bladeRF transmit and HackRF
receive, and VA6GA using the B210 for transmit and receive. The measured
operating settings recorded for this path are B210 receive gain 52.0 dB,
bladeRF txvga2 10 and txvga1 -4. The handoff
explicitly warns that B210 receive gain 70.0 dB compresses the front end
in this feet-apart bench geometry.
This result corrects the previous "antenna path blocked" interpretation. The antenna path was not the missing piece; the live software was.
The first live QSO was minutes-latency because the station decoded
whole receive windows. The usability pass crops each non-silent receive
window to the traffic span before decoding. On the retained real B210
observer capture
run-artifacts/solo-usrp-observer/raw_rx.cf32, which
contains 16,000,000 IQ samples over 8 s, the crop spans samples
1,200,000 to 5,400,000. The handoff records 19.0 s for the full-window
decode and 4.6 s for the cropped decode, a 4.2x improvement, with the
same payload
b'MSA1\x06\x05\x00\x11VE6SLPVA6GAB210 solo observe'.
Re-running the same measurement during local verification produced the
same crop and byte-identical payload, with a similar roughly 4x
speed-up.
The B210 receive window was also lengthened to amortize UHD setup cost. The handoff records about 2.0 s of B210 deaf time per receive window, or about 80% duty cycle with 8 s windows, while the HackRF has effectively no such gap. In one measured one-way run, 8 s USRP windows delivered 2 of 5 messages and 16 s windows delivered 5 of 5. At the 16 s default, a symmetric five-message exchange between VE6SLP and VA6GA delivered 4 of 5 messages in both directions, with 0 rejected frames and 0 station errors. That is usable, but it is not 5 of 5 and it is still a small-count result. The handoff records an end-to-end latency example of about 30 s: a message queued at 09:35:47 was on the far operator's screen at 09:36:22.
The preceding attempt, run-artifacts/three-radio-uhf-1/,
is also useful engineering evidence. It used the same independent-radio
topology but bladeRF txvga2 was 10 dB. The receiver
recovered only VE6SLP, rejected VE6NAS at the
header stage, and stopped with
no CRC-valid candidate in residual generation. The
successful run lowered bladeRF txvga2 to 0 dB rather than
raising the B210, keeping total radiated power down while bringing the
received signals closer together.
tools/three_radio_congested.py also carries a
--bladerf-lead-seconds option, defaulting to 1.05 s,
because the bladeRF CLI loads the FPGA image before streaming and
otherwise keys late relative to the other process.
The solo calibration captures explain the power-balance fix. Using
the same HackRF observer and the 100 ms envelope buckets, the B210 at
gain 52 measured a peak mean magnitude of 0.0833497866988182. The
bladeRF at txvga2 0 measured 0.09547556936740875, which is
+1.1797552840885095 dB relative to that B210 reference. The bladeRF at
txvga2 10 measured 0.30271145701408386, which is
+11.202487313470115 dB relative to the same reference. That 11.2 dB
imbalance sits on the no-channel software near/far bound already
reported in the handoff: UHF recovers both packets at -10 dB but loses
one by -15 dB. The failed three-radio attempt is therefore consistent
with a link-budget problem, not an arbitrary station-count limit.
On-air congested/composite hardware results
These are measured-on-hardware captures, but the congested signal was produced by one radio transmitting a digitally superimposed composite. The component bursts have distinct start times, carrier offsets and amplitudes, but they share one local oscillator, one power amplifier and one timebase. These runs therefore test receiver cancellation under controlled overlap; the independent three-radio result above is the stronger hardware claim for uncoordinated transmitters.
| Artifact | Profile | Composite transmitters | Receiver result | Burst | Stop condition | Evidence |
|---|---|---|---|---|---|---|
run-artifacts/on-air-collision-1/ |
HF | second burst +3.5 s, +180 Hz, amplitude 0.7 | CRC-valid VE6SLP only; VA6SLP rejected at
header |
16.769 / 16.8 s, complete | no CRC-valid candidate in residual generation | collision-run-manifest.json |
run-artifacts/on-air-collision-2/ |
HF | second burst +3.5 s, +180 Hz, amplitude 1.0 | CRC-valid VE6SLP, VA6SLP |
16.769 / 16.8 s, complete | all unique catalogue entries cancelled | collision-run-manifest.json |
run-artifacts/uhf-on-air-congested-1/ |
UHF | second burst starts at 0.85 s, +180 Hz, amplitude 0.85 | CRC-valid VE6SLP, VA6SLP |
1.759 / 1.98 s, incomplete envelope | all unique catalogue entries cancelled | collision-run-manifest.json |
run-artifacts/usrp-observer-2tx-hi/ |
UHF | B210 observer; bladeRF radiates two asynchronous logical stations, second +0.35 s, +600 Hz, amplitude 0.8 | CRC-valid VE6SLP, VE6NAS |
peak 100 ms mean magnitude 0.0802842453122139; minimum envelope bucket 0.005275574978441 | all unique catalogue entries cancelled | three-radio-manifest.json,
usrp-rx.log |
The UHF congested run's burst_complete is false. The
handoff explains that the envelope detector loses the quieter
transmitter's tail, while the receiver still decodes from the
samples.
The B210 observer run is a front-end independence result, not an independent-transmitter result: both logical stations are radiated by the bladeRF in one composite waveform, while the independent part is the receiving station.
Simulated/software results
These are not on-air measurements.
| Result | Label | Evidence |
|---|---|---|
| Both MOSAIC-HF and MOSAIC-UHF show an Eb/N0 cliff at about +1 dB in a matched-noise software sweep. | Simulated | docs/RF-BENCH-HANDOFF.md |
| MOSAIC-HF audio ITU-R F.1487-style simulations recover 240/240 through -9 dB SNR (7.8 dB Eb/N0); the flat AWGN threshold is bracketed between +1.8 and -1.2 dB Eb/N0, while the fading cases cost about 3 dB near threshold. | Simulated channel model, not on-air HF | run-artifacts/hf-channel/itu-f1487.json,
itu-f1487-waterfall.json,
itu-f1487-cliff.json |
| MOSAIC-HF audio multi-user simulations work without fading but under ITU-style HF fading recover only 1-2 stations at 4 and 8 stations, stopping with no CRC-valid residual candidate. | Simulated channel model, not on-air HF | run-artifacts/hf-multiuser/hf-audio-nofade.json,
run-artifacts/hf-multiuser/hf-audio-scaling.json |
| At fixed transmit power, the UHF profile collects 12 dB more noise in its wider bandwidth. | Analytical/software-source claim in handoff | docs/RF-BENCH-HANDOFF.md |
| UHF software test cancels a neighbour 6 dB down in a two-transmitter composite. | Simulated, no RF channel | tests/test_profiles.py |
| Handoff near/far sweep reports HF: 2/2 at 0 dB, -3 dB and -10 dB; UHF: 2/2 at 0 dB, -3 dB, -6 dB and -10 dB, then 1/2 at -15 dB and -25 dB. | Simulated | docs/RF-BENCH-HANDOFF.md |
Scaling files without an even_spacing key are
superseded and must not be quoted as current MOSAIC performance. For the
old unfaded files, the cause was the sweep tool's signal generator: it
produced double-sideband mirror images instead of true frequency
offsets. |
Superseded simulated scaling results | run-artifacts/scaling/README.md,
uhf-balanced-12-seeds.json,
uhf-balanced-knee.json,
uhf-balanced-many.json, uhf-nofade.json,
uhf-nofade-8.json, uhf-faded-seeds.json,
uhf-spread0-8.json, uhf-spread12-8.json,
hf-balanced.json |
Current no-fade UHF random-arrival seeds with 0.0 dB amplitude
spread, 0.6 s arrival span and +/-800.0 Hz CFO spread recovered 12/12
and 12/12. Both stopped with
all unique catalogue entries cancelled. |
Simulated; current harness, no fading, no AWGN added by
tools/many_station_scaling.py |
run-artifacts/scaling/uhf-analytic-12-seeds.json,
tools/many_station_scaling.py |
Current no-fade UHF random-arrival seed-7 re-sweep with 0.0 dB
amplitude spread recovered 8/8, 10/10, 12/12 and 16/16. Every run
stopped with all unique catalogue entries cancelled. |
Simulated; current harness, no fading, no AWGN added by
tools/many_station_scaling.py |
run-artifacts/scaling/uhf-analytic-resweep.json,
tools/many_station_scaling.py |
Current no-fade UHF random-arrival seed-7 deep sweep with 0.0 dB
amplitude spread recovered 22/24 and 26/32. Both larger runs stopped
with
no CRC-valid candidate in residual generation after copy-only interference projection. |
Simulated; current harness, no fading, no AWGN added by
tools/many_station_scaling.py; single seed at each
count |
run-artifacts/scaling/uhf-analytic-deep.json,
tools/many_station_scaling.py |
| Current no-fade UHF even-spacing twelve-station runs recovered 8/12 at 0.0 dB amplitude spread and 5/12 at 6.0 dB spread. These are coordinated layouts, not uncoordinated random-arrival traffic. | Simulated; current harness, no fading, no AWGN added by
tools/many_station_scaling.py; even spacing |
run-artifacts/scaling/uhf-even-12-seeds.json,
run-artifacts/scaling/uhf-even-12-spread6.json |
| Current-code eight-station faded UHF recheck with 6.0 dB amplitude spread recovered 12/32 station opportunities over four independent draws, an outage rate of 62.5%. | Simulated; flat fading, no AWGN added by
tools/many_station_scaling.py |
run-artifacts/scaling/uhf-faded-recheck.json,
tools/many_station_scaling.py |
| Current-code eight-station faded UHF balanced run with 0.0 dB amplitude spread recovered 15/32 station opportunities over four independent draws, an outage rate of 53.1%. | Simulated; flat fading, no AWGN added by
tools/many_station_scaling.py |
run-artifacts/scaling/uhf-faded-balanced.json,
tools/many_station_scaling.py |
The simulated near/far sweep bounds the cancellation algorithm, not
the radio link. The previous no-fade population scaling interpretation
is withdrawn. In the defective unfaded path, each station's real audio
was cast to complex and rotated, then emitted as
real(audio * exp(j2*pi*f*t)), which is
audio * cos(2*pi*f*t). That is double-sideband AM: it
halves the wanted component and adds a mirror image of the entire
station reflected about DC. The mirror matches no catalogue entry, so
SIC cannot cancel it, and the error scales with station count.
src/mosaic_hf/channel.py always applied a Hilbert transform
first; only the unfaded branch in
tools/many_station_scaling.py skipped it.
tests/test_many_station_scaling.py now pins this defect by
asserting that the image is more than 40 dB below the wanted
component.
The current scaling provenance rule is simple: quote only files whose
result records carry an even_spacing key. Files without
that key are superseded; run-artifacts/scaling/README.md
shows that old unfaded files can disagree sharply with current code on
identical inputs because the sweep tool generated the wrong signal, not
because the receiver or protocol changed. The analytic and
balanced words in filenames are lab labels, not tool modes
or distinct channel models.
The fixed no-fade branch now agrees with the twelve-station hardware
evidence for the retained seeds that have landed: the same two
twelve-station seeds that previously failed in superseded files now
recover 12/12 and 12/12, and the current seed-7 random-arrival sweep
recovers every station through 16: 8/8, 10/10, 12/12 and 16/16. Sixteen
was the size of the signature pool used by that run, not a receiver
limit. tools/many_station_scaling.py now contains 32
signatures, and tests/test_many_station_scaling.py checks
that those signatures yield 32 distinct preamble sequences, which is
what acquisition actually observes. The current deep random-arrival
sweep recovers 22/24 and 26/32; those two large-count points are single
seeds, so they show graceful degradation rather than an outage rate.
The old scaling artifacts provide no current congestion-collapse curve, HF scaling curve or near/far population rank-order conclusion to quote. Current even-spacing files are also not interchangeable with random-arrival results: even spacing is a coordinated layout, while random arrival and carrier offsets are the clean uncoordinated case.
The faded current-harness runs show a separate many-station problem: fading can set delivery rate when the packet has no diversity across the fade. This is a link-budget outage, not a receiver limitation; when a station is in a deep fade for the duration of its burst, the energy never arrives for any receiver to recover. The no-fade results above are pure mutual-interference simulations with no AWGN added, so they do not measure delivery in a fading channel. Combined with the current 53.1%-62.5% eight-station flat-fading outage results, the live many-station picture remains materially worse than any no-fade count alone suggests.
The current headline is therefore narrower and stronger than the withdrawn collapse story. In a clean no-fade, no-AWGN channel with random arrivals, every station is recovered through 16, then the single-seed deep sweep recovers 22/24 and 26/32. Under flat fading, only 2 to 5 of 8 stations recover in each current draw: 62.5% total outage with 6 dB amplitude spread and 53.1% with levels balanced. Balancing levels improves that eight-station faded result by about nine percentage points, but does not engineer the outage away. The binding constraint shown by the retained simulations is the fading channel, not multi-user interference in the clean channel.
HF audio channel and real-time receive validation
ITU-R F.1487-style simulated HF channels
run-artifacts/hf-channel/itu-f1487.json and
run-artifacts/hf-channel/itu-f1487-waterfall.json record
the current tools/hf_channel_validation.py sweeps for
MOSAIC_HF_AUDIO. These are simulated channel-model results,
not on-air HF contacts and not measurements of ionospheric
propagation.
The first table is a sanity floor for multipath and Doppler
behaviour, not a noise-sensitivity headline. Because MOSAIC-HF uses
B_ref = 2400 Hz and R_b = 50 bit/s, each SNR
value corresponds to Eb/N0 = SNR + 16.8 dB; these initial
points therefore have enormous energy-per-bit margin. Each cell contains
10 trials.
| Channel | Delay spread | Doppler | 20 dB SNR / 36.8 dB Eb/N0 | 15 dB / 31.8 dB | 10 dB / 26.8 dB | 5 dB / 21.8 dB |
|---|---|---|---|---|---|---|
| Flat | 0.0 ms | 0.0 Hz | 10/10 | 10/10 | 10/10 | 10/10 |
| Good | 0.5 ms | 0.1 Hz | 10/10 | 10/10 | 10/10 | 10/10 |
| Moderate | 1.0 ms | 0.5 Hz | 10/10 | 10/10 | 10/10 | 10/10 |
| Poor | 2.0 ms | 1.0 Hz | 10/10 | 10/10 | 10/10 | 10/10 |
The aggregate sanity-floor result is 160/160 recovered. The physical interpretation is that the MOSAIC-HF 80 ms symbol is 40 times longer than the poor channel's 2 ms delay spread, so multipath smears across 2.5% of a symbol. The non-coherent 16-FSK detector also does not require carrier phase tracking, so a 1 Hz Doppler process is not a phase-lock problem for the demodulator.
The follow-up low-SNR waterfall extended the same four channels down to -9 dB SNR, still in the 2400 Hz reference bandwidth. Each cell contains 12 trials.
| Channel | Delay spread | Doppler | 2 dB SNR / 18.8 dB Eb/N0 | 0 dB / 16.8 dB | -3 dB / 13.8 dB | -6 dB / 10.8 dB | -9 dB / 7.8 dB |
|---|---|---|---|---|---|---|---|
| Flat | 0.0 ms | 0.0 Hz | 12/12 | 12/12 | 12/12 | 12/12 | 12/12 |
| Good | 0.5 ms | 0.1 Hz | 12/12 | 12/12 | 12/12 | 12/12 | 12/12 |
| Moderate | 1.0 ms | 0.5 Hz | 12/12 | 12/12 | 12/12 | 12/12 | 12/12 |
| Poor | 2.0 ms | 1.0 Hz | 12/12 | 12/12 | 12/12 | 12/12 | 12/12 |
The aggregate low-SNR waterfall result is 240/240 recovered. That is expected: -9 dB SNR is still 7.8 dB Eb/N0 after the 16.8 dB processing gain of the 50 bit/s HF profile.
run-artifacts/hf-channel/itu-f1487-cliff.json then
brackets the sensitivity cliff. Each cell contains 12 trials.

| SNR | Eb/N0 | Flat | Good | Moderate | Poor |
|---|---|---|---|---|---|
| -12 dB | +4.8 dB | 12/12 | 8/12 | 7/12 | 10/12 |
| -15 dB | +1.8 dB | 12/12 | 0/12 | 0/12 | 0/12 |
| -18 dB | -1.2 dB | 0/12 | 0/12 | 0/12 | 0/12 |
| -21 dB | -4.2 dB | 0/12 | 0/12 | 0/12 | 0/12 |
| -24 dB | -7.2 dB | 0/12 | 0/12 | 0/12 | 0/12 |
The flat AWGN threshold lies between the tested points of +1.8 and -1.2 dB Eb/N0: the flat channel is 12/12 at -15 dB SNR and 0/12 at -18 dB SNR. The transition from full recovery to zero recovery over one 3 dB step is expected for a well-matched K=7 rate-1/2 convolutional code, not a receiver instability.
The three fading channels all remain decodable at -12 dB SNR (+4.8 dB Eb/N0) and all fail at -15 dB SNR (+1.8 dB Eb/N0), where the flat channel is still perfect. The retained data therefore show about a 3 dB threshold cost for the ITU-R F.1487-style multipath and Doppler cases. The -12 dB faded counts are not ranked against each other: with 12 trials, their binomial standard error is about 1.4 counts, so 8/12, 7/12 and 10/12 are overlapping small-sample outcomes. The supported statement is that all three fading channels degrade together near threshold, recovering roughly 60-85% at that point.
HF limits one-pager

MOSAIC-HF at alpha is a proven waveform and single-user decoder with a clear shared-channel hole. The figure compresses four facts:
- Single-user sensitivity (simulated F.1487). Flat
AWGN brackets between +1.8 and −1.2 dB Eb/N0
(
Eb/N0 = SNR_2400Hz + 10·log₁₀(2400/50) = SNR + 16.8 dB). Good/moderate/poor multipath cost about 3 dB near threshold. - Shared-channel no-fade vs fade (simulated
hf-audio). Without fading the receiver peels through four stations and most of eight. Under ITU-style fading the absolute recovered count saturates near one or two stations. - Proven checklist. Real converter loopback, streaming throughput checks and single-user channel-model cliffs are in; on-air HF contacts, transceiver-in-the-loop ionospheric runs and faded multi-user success are out.
- Mechanism. Constant-gain cancellation over a ~13 s packet leaves a time-varying residual that buries weaker users after the first peel. UHF multi-user on-air success does not contradict this: the lab path is essentially static.
HF shared-channel simulation: no-fade success, fading collapse
tools/many_station_scaling.py --profile hf-audio now
measures concurrent MOSAIC-HF audio populations with independent start
times, carrier offsets, amplitudes and seeded channel processes. These
are simulations, not on-air HF contacts. The retained artifacts are
run-artifacts/hf-multiuser/hf-audio-nofade.json and
run-artifacts/hf-multiuser/hf-audio-scaling.json.
| Stations | No fading | ITU-style HF fading |
|---|---|---|
| 2 | 2/2, 2/2, 2/2 | 2/2, 1/2, 2/2 |
| 4 | 4/4, 4/4, 4/4 | 1/4, 1/4, 2/4 |
| 8 | 8/8, 6/8, 6/8 | 1/8, 2/8, 2/8 |
The no-fade control isolates the variable: in a static channel the HF
receiver recovers all stations through four and most or all of eight.
Under ITU-style HF fading, the absolute recovered count saturates at one
or two even as the population grows; eight stations yields the same
absolute count as two. That is a hard ceiling in this receiver, not
graceful degradation. Non-complete faded runs stop with
no CRC-valid candidate in residual generation, and the
four- and eight-station faded failures use the copy-only interference
projection stop path.
The mechanism is cancellation error in a time-varying channel. SIC reconstructs the strongest decoded station and subtracts it, but the replica is only as good as the channel estimate. A MOSAIC-HF packet runs about 13 s and an ionospheric channel does not hold still for 13 s. The first station can decode before any cancellation is needed; later stations depend on a subtraction whose residual can bury what remains underneath. This is a receiver limitation, not a waveform limitation: the one-station waveform decodes through the F.1487 channel set, while the current canceller lacks an in-packet channel tracker.
This also explains why UHF escapes the failure in the retained hardware. Radios a few feet apart in a static lab present an essentially non-fading channel, matching the no-fade column where HF also works. UHF does not have a better receiver; its retained shared-channel runs simply do not exercise the time-varying HF channel that breaks cancellation.
A dummy-load HF transceiver test would likewise reproduce the no-fade column. It would be valuable for qualifying rig audio, ALC behaviour, drive level and keying, but it would not predict on-air shared-channel behaviour because the dummy load removes the Doppler and multipath that break HF multi-user cancellation.
The real converter path reinforces the headroom issue. In
run-artifacts/hf-multiuser/real-2.json, two overlapping
stations through the Scarlett 4i4 converters recovered 1/2 when the
second station was 4.6 dB weaker;
run-artifacts/hf-multiuser/real-2-equal.json recovered 0/2
at equal power. The same unequal mixture decoded offline at 2/2 in
run-artifacts/hf-multiuser/offline-2.json. Equal power is
hard for peeling because there is no strongest station to remove first,
and the converter path reduces headroom because transmit drive is capped
at 0.25 peak to avoid SSB ALC while summing stations raises crest
factor.
Continuous-reception real-time requirement
A continuous receiver must decode faster than real time. Otherwise backlog and operator-visible latency grow for as long as the station runs. This is a correctness property of the live modem, not merely an optimization target.
The original MOSAIC-HF acquisition grid remains appropriate for
free-running SDR oscillators: it searches +/-2000 Hz and drift
hypotheses. MOSAIC_HF_AUDIO uses the same waveform as
MOSAIC-HF but narrows the acquisition search to +/-100 Hz at 12.5 Hz
coarse steps and removes drift hypotheses. The reason is physical. A
transceiver audio path occupies 200-2800 Hz inside an SSB filter; a 2000
Hz dial/audio error would move the waveform to 2200-4800 Hz, outside the
passband. What remains is rig clock error between stations tuned to the
same dial frequency; the source notes that 1 ppm at 14 MHz is 14 Hz
each, so +/-100 Hz is generous. The source also records that the wider
search decodes a 13 s packet in 273 s, about 19 times slower than real
time.
Local retained real-time checks with
tools/hf_realtime_check.py use simulated 20 dB SNR streams,
20 s receive windows and 15 s overlap. They exercise the streaming
requirement that a window may contain more than one transmission: the
consumer advances by PacketDecodeResult.packet_end_sample
rather than stopping at the first packet.
| Artifact | Audio duration | Transmissions | Decode time | Real-time factor | Recovered | Median latency | Worst latency | Backlog |
|---|---|---|---|---|---|---|---|---|
run-artifacts/hf-realtime-check/minutes5.txt |
300 s | 16 | 88.8 s | x0.296 | 14/16 | 11.2 s | 17.1 s | 0.0 s |
run-artifacts/hf-realtime-check/minutes12.txt |
720 s | 38 | 153.3 s | x0.213 | 30/38 | 9.4 s | 24.5 s | 0.0 s |
Latency is measured from the end of a transmission to the text being available and includes waiting for the receive window to close. These are simulated streaming checks of decoder throughput and window handling; they are not HF on-air QSOs.
Alpha release claims
This section is the publish boundary for the alpha tree. A row marked claimed is backed by retained artifacts or executable constants cited in this paper. A row marked not claimed is either unmeasured, failed in retained work, or still research. Marketing language that exceeds this matrix is out of scope for alpha.
Capability matrix
| Capability | Status | Evidence class | Anchor |
|---|---|---|---|
| P1 wire format (header CRC-8/ATM, payload CRC-16, K=7 rate-1/2, 16-of-192 hop) | Claimed | Executable constants | framing.py, waveform.py,
frame_bit_layout.png |
MOSAIC-HF 50 bit/s and MOSAIC-UHF 800 bit/s profiles
(Δf·T_s = 1) |
Claimed | Executable constants | profiles.py, band-profile table |
Eb/N0 conversion SNR_ref_dB + 10·log₁₀(B_ref/R_b) (=
+16.8 dB for both profiles) |
Claimed | Definition + tables | Mathematical specification |
CRC-gated SIC with immutable r₀ and LIFO undo |
Claimed | Implementation + flowchart | sic.py, sic_flowchart.png |
| Catalogue-bounded multi-user search (not unlimited blind active set) | Claimed | Implementation | sic.py, acquisition config |
| Cabled CRC-valid HF-profile and UHF-profile point-to-point on bench SDRs | Claimed | Measured hardware | cabled vendor manifests |
| On-air single-packet UHF and HF-profile-on-UHF-carrier CRC recovery | Claimed | Measured hardware | on-air vendor / solo manifests |
| Independent two-transmitter on-air UHF decode by a third radio | Claimed | Measured hardware | three-radio-uhf-2/ |
| On-air UHF 4/4, 8/8, 12/12 logical stations on two RF chains (incl. random arrivals) | Claimed | Measured hardware | onair-4station-imbalanced/,
onair-8station/, onair-12station/,
onair-12station-random/ |
| Live bidirectional keyboard QSO at 431.200 MHz (KISS modem) | Claimed | Measured hardware / handoff | live QSO section; small-count |
| MOSAIC-HF real DAC→analogue→ADC loopback byte-exact | Claimed | Measured converters | Scarlett 4i4 3/3 |
| MOSAIC-HF single-user F.1487-style cliff (~+1.8…−1.2 dB Eb/N0 flat; ~3 dB fade cost) | Claimed | Simulated channel model | hf-channel/itu-f1487*.json,
hf_itu_sensitivity.png |
| MOSAIC-HF no-fade multi-user peel in audio simulation | Claimed | Simulated | hf-multiuser/hf-audio-nofade.json |
| UHF no-fade software scaling complete through 16; graceful 22/24, 26/32 | Claimed | Simulated, current harness | scaling/uhf-analytic-*.json (files with
even_spacing) |
| Continuous HF-audio decode faster than real time with zero backlog (sim streams) | Claimed | Simulated streaming | hf-realtime-check/ |
Continuous UHF live demod keep-up (packet walk + residual peel +
multi-ROI crop, uhf-live) |
Claimed | Simulated streaming | tools/uhf_realtime_check.py (wall ratio ≪ 1) |
| Operator web panel (TX inhibit, transcript, receiver health) | Claimed | Implementation | src/mosaic_hf/operator_panel.py,
--ui-port |
| Fail-closed FPGA pin, burst-duration check, envelope profile, capture SHA binding | Claimed | Bench methodology | guardrails section |
| Twelve independent RF converters on air | Not claimed | — | two chains carry logical composites |
| Three simultaneous independent RF transmit chains | Not claimed | — | unmeasured |
| On-air HF ionospheric contact / transceiver-in-the-loop QSO | Not claimed | — | no retained HF on-air contact |
| MOSAIC-HF shared-channel success under ITU-style fading | Not claimed | Simulated failure | hf-audio-scaling.json (≈1–2 recovered) |
| Robust UHF multi-user under fading | Not claimed | Simulated partial | faded 8-station means ≪ 8 |
| Tracked-gain SIC alone solves HF multi-user fading | Not claimed | Root-cause diagnosis | HF limits section; BIC-selected tracking present, fade recovery still limited |
| Spectrum compliance, spurious, antenna or regulatory approval | Not claimed | — | out of scope |
| Instant messaging / sub-second chat | Not claimed | Air time is the floor | UHF packet ≈2.6 s; HF packet ≈40 s; continuous demod keeps up with the stream but does not shrink airtime |
| Joint ML multi-user receiver / unlimited blind discovery | Not claimed | — | architecture is CRC-gated peel |
Superseded no-fade scaling files without
even_spacing |
Withdrawn | Harness defect | scaling/README.md |
What alpha is for
Alpha is a deterministic reference modem and validation method: inspectable constants, CRC-gated cancellation, retained captures, and an honest split between radiated UHF multi-user proof and HF single-user / audio-path proof. It is not an HF multi-user service claim and not a twelve-radio field network.
Required citations when quoting alpha
- Radiated multi-user → cite the specific
run-artifacts/onair-*orthree-radio-*manifest and the two-chain topology. - Sensitivity / Eb/N0 → cite the SNR reference bandwidth and the +16.8 dB conversion; do not quote SNR as if it were Eb/N0.
- Scaling beyond hardware → cite only current-harness files that carry
even_spacing, and label simulation. - HF → say “audio path / channel model” unless a future on-air HF artifact exists.
Known limitations and future work
- Same-frequency simultaneous duplex was deliberately cut. The handoff notes that with 20-30 dB of antenna isolation, a bladeRF's own transmission could arrive at its receiver comparable to or stronger than the remote signal, risking front-end compression. The live station is half duplex.
- Fading is the binding measured gap. Current-code eight-station UHF fading simulations recover only 12/32 station opportunities with 6.0 dB amplitude spread and 15/32 with 0.0 dB spread. On MOSAIC-HF audio, fading is more severe for the shared channel: current simulated multi-user runs saturate at one or two recovered stations under ITU-style fading. Closing this requires diversity across a fade and, for HF multi-user operation, a canceller whose channel estimate tracks within the packet.
- Live keyboard latency is now usable but still not interactive-chat fast. Cropping reduced the retained B210 observer decode from a full-window 19.0 s to a cropped 4.6 s, and the handoff records about 30 s end-to-end delivery in a live exchange. The previous minutes-latency statement is superseded, but this remains delayed weak-signal text rather than instant chat.
- Receive duty cycle improved, but delivery is still a small-count result. The B210 loses about 2.0 s to UHD setup per receive window, so its default window is now 16 s. A one-way trial improved from 2/5 at 8 s windows to 5/5 at 16 s windows, and the symmetric 16 s exchange delivered 4/5 in both directions with 0 rejected frames and 0 station errors. Four of five is not five of five, and no long-run statistics have been measured.
- No-fade scaling had to be retracted and re-run.
Every previously reported
--no-fadescaling result fromtools/many_station_scaling.pyused a defective sweep-tool generator that manufactured mirror-image interference. This was a measurement-harness defect, not a receiver or protocol limit. Current random-arrival no-fade artifacts recover 12/12 and 12/12 for two seeds, then 8/8, 10/10, 12/12 and 16/16 in a seed-7 re-sweep. The single-seed deep run recovers 22/24 and 26/32, so degradation beyond 16 is graceful rather than a collapse or ceiling. - HF shared-channel operation is not yet proven.
Current
hf-audiosimulations recover 2/2, 4/4 and up to 8/8 without fading, but ITU-style HF fading collapses the absolute recovered count to one or two stations. The retained converter-path multi-user checks are also partial: 1/2 with a 4.6 dB weaker second station and 0/2 at equal power. - Load and spacing control remain open design questions. The current corrected artifacts do not show no-fade multi-user collapse, but the 24- and 32-station points are single seeds. Admission control or spacing control may still be useful for a practical many-user service, but the retained artifacts do not yet measure such a control mechanism.
- On-air near/far evidence is limited. Equal-power and modest unequal-power composite cases have been measured, the imbalanced four-station run recovered a substantially unequal population, and the first independent-radio attempt failed until the bladeRF transmit gain was lowered. Scaling needs per-station received-power management, not merely more transmitted power.
- Many-user scaling remains the open research question. The current independent-radio hardware evidence demonstrates two independent RF transmit chains decoded by a third radio. The twelve-station runs put six logical stations on each chain; they are not twelve-independent-radio claims, but the random-arrival run does show that random timing and offset placement did not break the retained twelve-station hardware case. The open problem is therefore fading first, then power control, receiver dynamic range, admission control and cancellation-chain robustness for a population of uncoordinated stations at widely differing ranges and time/frequency spacings.
- HF is not yet an on-air HF contact. The bench now has a real path for MOSAIC-HF through a transceiver audio interface and has decoded byte-exact through real DAC/ADC converters. It also has simulated ITU-R F.1487 channel results and a direct-radiated UHF-carrier SDR partial that acquired but did not decode. The direct SDR HF RF path is still impossible here: the measured limits are 237.5 MHz for the bladeRF x115, 42 MHz for the B210, and a half-duplex HackRF. A real transceiver and ionospheric path remain unmeasured.
- Direct-radiated narrow tone grids need better references or finer drift search. The MOSAIC-HF-on-UHF SDR partial is consistent with drift across 12.5 Hz tones. Future direct-radiated use of that grid requires either a disciplined frequency reference or a finer drift hypothesis grid than the current -10, 0 and +10 Hz/s search.
- Per-radio keying latency matters. The B210 can be scheduled by wall clock; the bladeRF CLI must load its FPGA before streaming. The retained three-radio tool compensates with a 1.05 s default bladeRF lead, but larger networks will need explicit timing and latency accounting.
- SoapySDR path is not the proven long-payload route. The handoff records Soapy captures that recovered headers but failed payload CRC, while vendor tools produced CRC-valid cabled exchanges.
- B210 receive gain is operationally important. The
B210 is proven as a decoding observer in
usrp-observer-2tx-hi, but the lower-gainusrp-observer-2txrun recovered no CRC-valid payloads. The envelope profile shows that was an under-gained capture rather than evidence of a waveform capacity limit.
Reproducing retained result types
The following are the exact command forms recorded in the source and handoff. They are reproduction instructions, not commands run while writing this paper.
Cabled or on-air point-to-point run
python tools/cabled_bladerf_hackrf.py --output run-artifacts/<name> \
--mode cabled --frequency-hz 431200000 --profile uhf --capture-seconds 4 \
--fpga hostedx115-latest.rbf --source VE6SLP --destination VA6GA \
--text "MOSAIC-UHF cabled first light" --executeReverse direction:
python tools/cabled_bladerf_hackrf.py --output run-artifacts/<name> \
--mode on-air --callsign VE6SLP --frequency-hz 431200000 --profile uhf \
--direction hackrf-to-bladerf --hackrf-tx-gain-db 20 --capture-seconds 4 \
--fpga hostedx115-latest.rbf --destination VE6SLP \
--text "MOSAIC-UHF on air reply" --executeCongested composite run
python tools/onair_collision.py --output run-artifacts/<name> \
--mode on-air --callsign VE6SLP --frequency-hz 431200000 --profile uhf \
--txvga2-db 10 --capture-seconds 8 --fpga hostedx115-latest.rbf \
--transmitter "VE6SLP:VA6GA:0x2f:0.20:0.0:1.0:alpha" \
--transmitter "VA6SLP:VE6NAS:0x5b:0.85:180.0:0.85:bravo" --executeThree-radio independent-transmitter run
python tools/three_radio_congested.py --output run-artifacts/<name> \
--callsign VE6SLP --frequency-hz 431200000 --profile uhf \
--capture-seconds 8 --txvga2-db 0 --usrp-gain-db 52 \
--transmitter "bladerf:VE6SLP:VA6GA:0x2f:0.0:0.0:1.0:independent alpha de VE6SLP" \
--transmitter "usrp:VE6NAS:VA6SLP:0x5b:0.0:0.0:1.0:independent bravo de VE6NAS" \
--fpga hostedx115-latest.rbf --executeCarrier leakage measurement
python tools/tx_carrier_leakage.py --output run-artifacts/<name> \
--frequency-hz 431200000 --callsign VE6SLP --destination VA6GA \
--profile uhf --txvga2-db 10 --fpga hostedx115-latest.rbf --executeDo not add --calibrate for the retained operating mode;
the measured calibrated run made carrier leakage much worse.
Offline retained-capture decode
python tools/decode_capture.py --capture run-artifacts/<name>/raw_rx.cs8 \
--output run-artifacts/<name>/offline-decode.json --profile uhf \
--format cs8 --candidate CALLSIGN-1:0x2f --candidate CALLSIGN-2:0x5bFor CF32 observer captures, pass --format cf32. The
output records the capture SHA-256 so the decode remains tied to the
exact recording.
Live KISS modem
python tools/mosaic_modem.py --callsign VE6SLP --peer VA6GA --profile uhf \
--frequency-hz 431200000 --fpga hostedx115-latest.rbf --executeThe live station exposes KISS on 127.0.0.1:8001 by
default and runs an RX -> guard -> TX -> guard
cycle.
MOSAIC-HF transceiver audio path
List audio devices:
python tools/mosaic_hf_audio.py devicesRun the retained analogue-converter loopback form:
python tools/mosaic_hf_audio.py --amplitude 0.9 loopback --device 13 \
--repeat 3 --text "VE6SLP-1 DE VA6GA-2 QSL 599 K"For a real SSB transmitter, start with the default
--amplitude 0.25, use a serial
--ptt-port/--ptt-line where possible, and
raise drive only until ALC just begins to move.
Test environment note
tests/test_gnuradio_virtual_channel.py must be run with
/usr/bin/python3, not the pyenv interpreter. The repository
contains a gnuradio/ directory that can shadow the real GNU
Radio package as a namespace package from the repository root; the
failure only appears in subprocesses launched from elsewhere. With
system Python, the retained check is:
/usr/bin/python3 -m pytest tests/test_gnuradio_virtual_channel.py -qLimits of the current evidence
- Measured hardware evidence extends to two independent RF transmit chains active at once. Three-chain operation is unproven.
- The largest retained hardware population is twelve simultaneous logical stations carried across two RF chains. The 16-, 24- and 32-station results are simulated no-AWGN cases.
- The 24- and 32-station no-fade results are single seeds. They show graceful degradation in those runs, not an outage-rate estimate.
- Diversity across a fade and admission-control policy remain unmeasured.
- Long-run live-modem delivery statistics remain open. The retained QSO and 4/5-per-direction exchange are successful small-count operating results.
- MOSAIC-HF evidence is now precise but incomplete. The profile decoded byte-exact through a real DAC/analogue-wiring/ADC path, 3/3. Simulated ITU-R F.1487 flat/good/moderate/poor channels establish a flat threshold bracket of +1.8 to -1.2 dB Eb/N0 and about a 3 dB fading penalty near threshold for one station. Retained continuous-reception checks keep up at x0.296 and x0.213 real time with zero backlog in 300 s and 720 s simulated streams. Direct SDR radiation of the HF waveform on a UHF carrier acquired and correctly attributed the burst but did not decode it. Shared-channel HF simulations work without fading but collapse under ITU-style fading to one or two recovered stations, and real-converter two-station checks are partial at 1/2 and 0/2. MOSAIC-HF still does not have an on-air HF contact, ionospheric propagation result, real transceiver-in-the-loop measurement or proven shared-channel operation in a fading HF channel.
- On-air near/far evidence remains limited to equal-power or modest unequal-power composite cases, solo envelope calibration captures, and the two independent three-radio attempts.
- The HF sensitivity cliff is simulated channel-model evidence, not a measured-on-hardware waterfall or an on-air HF propagation result.