Metadata-Version: 2.4
Name: mosaic-hf
Version: 0.1.0a1
Summary: Executable reference model for an asynchronous multi-user HF modem
License-Expression: MIT
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: numpy>=2.0
Requires-Dist: scipy>=1.14
Provides-Extra: audio
Requires-Dist: sounddevice>=0.4; extra == "audio"
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"

# MOSAIC-HF

MOSAIC-HF is an experimental asynchronous multi-user modem for ordinary
2.8 kHz HF channels. Stations deliberately share the passband. A receiver
discovers overlapping bursts, decodes what it can, reconstructs valid
waveforms, subtracts them, and repeats.

The project is pursuing a usable amateur-radio mode, not a paper-only link
budget. The first hardware target is a FlexRadio-style SDR path through
audio/DAX; direct IQ can follow once the portable modem is sound.

## Current state

The programme has an **M1 bounded single-burst acquisition reference** and a
first **M2 offline bounded two-user SIC acceptance gate**. The latter is a
deterministic real-audio near/far overlap only: it proves that a CRC-valid
strong packet can be reconstructed from its decoded identity/payload and
receiver estimates, subtracted through a reversible ledger, and expose a weak
packet that failed before cancellation. It is not equal-power, four/eight-user,
Watterson/RF, real-time, or operational evidence. The remaining gates measure:

- occupied bandwidth and transmit envelope;
- packet error rate against SNR, frequency error, drift, and HF fading;
- asynchronous acquisition probability and false-alarm rate;
- multi-user collision and cancellation behaviour;
- actual payload throughput after synchronization and coding overhead.

Claims in the original proposal are hypotheses until those tests pass.

The repository now contains the first **M0-H1 waveform oracle**: deterministic
bit packing and 16-candidate-per-symbol mapping across a 192-bin shared grid,
continuous-phase real audio, bandwidth-referenced AWGN/CFO/drift, and a
perfect-timing/perfect-offset noncoherent receiver. Candidate mappings provide
frequency diversity, not codebook orthogonality. The oracle is a waveform and impairment foundation, not a sensitivity result.
See [`docs/M0-REFERENCE.md`](docs/M0-REFERENCE.md) for exact conventions and
open gates.

M1 adds a deterministic blind detector for one known 32-symbol access preamble
in longer **real-audio** streams with unknown integer sample timing. It searches
CFO from -112.5 to +112.5 Hz (therefore including +/-100 Hz) and a deliberately
bounded linear drift range of -3 to +3 Hz/s. It is an acquisition gate only:
there is no payload decode, FEC/CRC validation, SIC, or general sensitivity
claim. The original 12-search result is retained as a diagnostic only. The
separately invoked M1 campaign uses one full stream per independently spawned
NumPy `SeedSequence` child and reports exact Clopper--Pearson intervals and an
exact one-sided 95% bound for Pd and Pfa/search, including nonzero events. Its
pre-specified threshold is not calibrated from campaign scores. This is still
a finite real-audio-AWGN simulation result—not an operational false-alarm-rate
claim.

M2's small candidate manager accepts a declared ordered list of access-signature
hypotheses, retains immutable original samples, and searches the current
residual through the existing blind P1 `decode_packet` path. Only a successful
CRC-valid return may enter its ledger. The cancellation model fits complex
gain/phase and a bounded constant-CFO grid over the decoded extent while holding
drift to the blind receiver estimate; this is a deliberately limited real-audio
trajectory fit, not an HF channel estimator. The compact artifact is
[`experiments/m2-offline-two-user-sic-nearfar-offset-seed20260721-8trials.json`](experiments/m2-offline-two-user-sic-nearfar-offset-seed20260721-8trials.json).

## Start here

- [`PROJECT.md`](PROJECT.md) — charter, definition of working, and acceptance gates
- [`ROADMAP.md`](ROADMAP.md) — staged implementation plan
- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) — system decomposition and initial PHY baseline
- [`docs/DECISIONS.md`](docs/DECISIONS.md) — load-bearing design decisions
- [`docs/EXPERIMENT-LOG.md`](docs/EXPERIMENT-LOG.md) — measured evidence and changes of direction
- [`docs/TRANSMISSION-VISUAL-ATLAS.md`](docs/TRANSMISSION-VISUAL-ATLAS.md) — source-linked observer/TX-shape visual evidence and exact regeneration
- [`docs/SIX-NODE-SHARED-CHANNEL-CAMPAIGN.md`](docs/SIX-NODE-SHARED-CHANNEL-CAMPAIGN.md) — five independent TX nodes, one virtual CF32 medium, continuous receiver-only decoding, and retained two-run evidence
- [`docs/VIRTUAL-SOAPY-CONGESTED-REPLAY.md`](docs/VIRTUAL-SOAPY-CONGESTED-REPLAY.md) — replay that congested common capture through the RX-facing Soapy executor contract, retain fresh SigMF, and blind-decode it
- [`docs/whitepaper/mosaic-hf-whitepaper.typ`](docs/whitepaper/mosaic-hf-whitepaper.typ) — illustrated implementation-and-validation white paper; compile with `SOURCE_DATE_EPOCH=0 typst compile --root . ...`
- [`docs/NODE-RELEASE-READINESS.md`](docs/NODE-RELEASE-READINESS.md) — installable `mosaic-hf-node` BladeRF/HackRF profiles, fail-closed lifecycle, virtual two-node smoke, and cabled-node preflight
- [`docs/ORIGINAL-PROPOSAL.md`](docs/ORIGINAL-PROPOSAL.md) — source concept, preserved verbatim

## Local toolchain

The development host has Python/NumPy/SciPy, GNU Radio 3.10, and Octave 9.4.
Python is the executable reference model; GNU Radio is the streaming and SDR
integration harness. A Rust portable core is planned after the reference
algorithms and wire format stop moving.

### GNU Radio finite loopback

`mosaic_hf.gnuradio_loopback` now runs a finite GNU Radio 3.10 file/vector
flowgraph over canonical 12 ksample/s PCM16 MOSAIC audio. The actual graph is
chunked float source → `float_to_complex` → 4× rational interpolation with an
explicit 129-tap Hamming low-pass FIR → +3 kHz complex placement →
`multiply_cc` → translating 129-tap FIR/4× decimation → `complex_to_real` →
retained vector sink. The matched FIRs each declare 16 audio-sample group delay
and a net zero impulse-peak offset; the graph's finite drain tail is retained.

Run the actual GNU Radio gate with:

```bash
PYTHONPATH=src python3 -m pytest tests/test_gnuradio_loopback.py -q
```

Produce a canonical WAV first, then emit the reproducible JSON manifest (and
optionally retain the recovered WAV):

```bash
PYTHONPATH=src python3 -m mosaic_hf packet-produce --output /tmp/mosaic.wav \
  --payload-hex 4d312d574156 --signature 0x1234abcd
PYTHONPATH=src python3 tools/gnuradio_loopback.py --input /tmp/mosaic.wav \
  --signature 0x1234abcd --output-wav /tmp/mosaic-recovered.wav \
  --manifest /tmp/mosaic-gnu-radio-manifest.json
```

The manifest records GNU Radio version, path/rates/taps/latency, source work
quanta, input/output counts and SHA-256s, blind decoder/CRC result, and limits.
The decoder is the existing packet decoder; it is not given timing, CFO, drift,
length, or payload values. This proves only deterministic in-process GNU Radio
file/vector loopback; it does not prove RF/cabled loopback or over-the-air
behavior.

### Hardware adapter dry runs

`mosaic_hf.hardware_adapters` has a concrete direct-IQ Soapy Source/Sink plan,
a FlexRadio-style DAX file/mock adapter, UHF diagnostic-card parser, retained
SigMF IQ writer, and replay integration. `mosaic_hf.hardware_executor` now
provides the actual finite execution sequence through exported injected runtime
protocols: fresh exact re-probe, applied-setting readback, CF32/SigMF retention,
absolute fault ledger, finite stop, and immediate-inhibit cancellation. Its
bundled optional `mosaic_hf.soapy_runtime:create_runtime` adapter imports the
Python SoapySDR binding only after an explicit receive-only or cabled-bench
selection; normal imports and dry runs never enumerate/open a device. It has no
DAX/CAT/PTT or on-air path. The fixture below is an executable **dry run**, not
a command to use a radio:

```bash
PYTHONPATH=src python3 tools/rf_adapter_plan.py \
  --inventory fixtures/rf/soapy-bladerf-hackrf.txt \
  --card fixtures/rf/uhf-diagnostic-card.json
```

The card requires exact TX/RX identity, exact UHF frequency, named manual
gains, CF32, nonzero placement, finite burst/duty, an exact measured/bounded
1 mW conducted target, capture roll, and immediate-inhibit record. It also
requires raw RX SigMF, pre-SDR TX IQ, and event-ledger artifacts. It will reject
a no-device inventory or incomplete card; it cannot start TX even with a card.
For an attached cabled bench, install the maintained Python binding and driver
modules (for example `python3-soapysdr`, `soapysdr-module-bladerf`, and
`soapysdr-module-hackrf` on Debian/Ubuntu), then select
`--runtime-plugin mosaic_hf.soapy_runtime:create_runtime` explicitly. Missing
bindings/devices fail before stream setup with an actionable error. For the
exact cabled-bench and receive-only CLI modes, required fields, and physical
setup sequence, see [`docs/RF-CABLED-BENCH-TEST-CARD.md`](docs/RF-CABLED-BENCH-TEST-CARD.md).
DAX endpoint/rate/12 kHz/PTT-policy requirements are in
[`docs/FLEX-DAX-EXECUTION.md`](docs/FLEX-DAX-EXECUTION.md).

Run the current checks with:

```bash
python3 -m pytest
```

Run a seeded experiment (all output is machine-readable JSON):

```bash
python3 -m mosaic_hf simulate --seed 7 --symbols 256 \
  --snr-db -16 --cfo-hz 35 --drift-hz-per-s 0.5
python3 -m mosaic_hf golden check --path vectors/m0_golden.json
python3 -m mosaic_hf acquisition --seed 20260720 --trials 12 --noise-trials 12 \
  --snr-db -18 --stream-samples 36000
# Bounded, independently seeded campaign (about several minutes on a laptop):
python3 -m mosaic_hf acquisition-campaign --seed 20260720 --trials 8 \
  --noise-trials 24 --snr-db -18 --stream-samples 32000 --threshold 2.4
# Versioned, independently spawned AWGN-only blind packet evidence:
python3 -m mosaic_hf packet-campaign --seed 20260721 --trials 8 --snr-db -12
# Declared two-path finite-tone fading fixture; Watterson-style, not validated HF:
python3 -m mosaic_hf watterson-packet-campaign --seed 20260721 --trials 8 \
  --snr-db -9 --channel-cfo-hz 30 --channel-drift-hz-per-s 1
# Bounded deterministic component acceptance (impulses, hard clip, interferer):
python3 -m mosaic_hf channel-model-acceptance --seed 2026072103 \
  --trials-per-point 4
# Compact bounded two-user SIC evidence (linear real-audio addition only):
python3 -m mosaic_hf sic-campaign --seed 20260721 --trials-per-point 2
python3 -m mosaic_hf packet-produce --output /tmp/mosaic.wav \
  --payload-hex 4d312d574156 --signature 0x1234abcd
python3 -m mosaic_hf packet-consume --input /tmp/mosaic.wav \
  --signature 0x1234abcd
# Seeded noisy WAV exchange fixture (not a sensitivity claim):
python3 -m mosaic_hf packet-produce --output /tmp/mosaic-noisy.wav \
  --payload-hex 4d312d4e4f495359 --signature 0x1234abcd \
  --seed 20260720 --snr-db -9 --cfo-hz 30 --drift-hz-per-s 1 \
  --leading-silence-samples 137
python3 -m mosaic_hf packet-consume --input /tmp/mosaic-noisy.wav \
  --signature 0x1234abcd
```

`packet-produce` applies any requested AWGN/CFO/drift before canonical PCM16
quantization and records the seed and channel values in its JSON. The noisy
fixture is one deterministic exchange, not a PER or sensitivity measurement.

`acquisition-campaign` reports the derivation and count of independent streams,
search duration and total noise-search volume, SNR definition, detector
threshold, Pd/Pfa-search, and exact binomial uncertainty. Increase
`--noise-trials` for a tighter simulation bound; do not use the result as a
field false-alarm-rate specification.

The packet-campaign commands likewise report exact 95% Clopper--Pearson
*marginal* intervals for unconditional valid delivery, PER, CRC-valid false
delivery, and each mutually exclusive outcome. They make the finite simulated
sample explicit; the outcome categories share each trial and are not asserted
to be independent estimates.

`channel-model-acceptance` uses the versioned
`mosaic-hf.channel-manifest.v1` ordered pipeline. It records input/output
power, separate `SeedSequence` substreams, realized Bernoulli-Gaussian impulse
statistics, exact hard-clip fraction, and the timed sine interferer's declared
SIR plus FFT-bin energy overlap with the named 300--2700 Hz receiver band.
Its three fixed one-variable sweeps include a fixed −6 dB named-bandwidth AWGN
floor and use the blind P1 decoder without timing, CFO, drift, packet-length,
or payload oracles. This is deterministic simulator component coverage only,
not sensitivity, a validated HF channel, RF/front-end, or hardware evidence.

`sic-campaign` emits the versioned offline two-user near/far/offset matrix and
exact Clopper--Pearson marginal intervals for mutually exclusive outcomes.
The stronger packet is supplied only as part of constructing each received
mixture; cancellation regenerates it from the CRC-valid decoded packet and
mixture-derived fit, never from that clean construction array. The current
estimator is bounded to complex gain/phase plus a small constant-CFO search,
with drift held to the blind receiver estimate. It deliberately does not claim
equal-power separation, more than two users, Watterson/HF/RF behaviour,
real-time behavior, or operational performance.

For a source checkout that has not been installed, prefix those commands with
`PYTHONPATH=src`. Regenerate the canonical PCM16 vector only when an intentional
air-interface change is under review:

```bash
PYTHONPATH=src python3 -m mosaic_hf golden generate \
  --path vectors/m0_golden.json
```

## Non-negotiables

- Deterministic, public, bit-exact over-the-air protocol.
- A conventional receiver remains possible; neural assistance is optional.
- No semantic compression required for interoperability.
- Every sensitivity or capacity claim names its bandwidth, rate, channel
  model, and success criterion.
- Recorded samples and golden vectors are first-class test fixtures.
- Transmit inhibit, explicit identification, bandwidth reporting, and duty
  cycle accounting are part of the product—not UI afterthoughts.
