A browser chat application usually sends a message directly to another client. iqradio instead turns that message into a waveform, places it in a shared RF world, and asks each receiver’s modem to recover it. Distance, tuning, noise, antenna orientation, collisions, and multipath can all affect whether the bytes arrive.

This article is part of tsim. RF synthesis and spectrum analysis covers the sample-generation and monitoring foundations; this deep dive follows the packet through the transmitting and receiving radios.

From Browser Input to Received Bytes

Each browser console claims a scenario radio and controls its transmit and receive settings. The service retains radio claims, configuration, history, and transfer data. A local HTTP file upload only makes bytes available to the sender; RF transmission proceeds at the configured waveform’s rate.

graph LR
    browser["Browser text or file"] --> queue["Packet framing and transmit queue"]
    queue --> tx["Single-carrier or OFDM modulation"]
    tx --> world["Shared RF world: geometry, paths, noise"]
    world --> capture["Receiver capture"]
    capture --> acquire["Acquisition, timing, channel correction"]
    acquire --> decisions["Symbol decisions and packet validation"]
    decisions --> receiver["Reassembled text or file"]
    world --> monitor["Selected radio or probe IQ"]

Packets use a header, optional payload scrambling, CRC32C, and fragmentation. Completed files undergo SHA-256 verification. The current payload limit is 1 KiB, so a large file exercises many bursts and many receiver acquisitions.

There is no over-the-air acknowledgement or retry in version 1. Sender progress counts emitted fragments; receiver progress counts fragments successfully decoded at that receiver. A file can therefore finish transmitting while one receiver remains incomplete. This is a useful distinction when inspecting loss, but it also defines the current delivery semantics.

The World Has One Sample Clock

The world advances in sample frames, with simultaneous transmitters contributing to the same receiver environment. Link geometry is refreshed from current positions each frame. A moving transmitter can therefore change the channel during a retained burst; waveform and receive settings remain packet-scoped.

World bandwidth, receiver bandwidth, and waveform bandwidth are separate settings. A radio might listen to a narrow channel within a much wider simulated world. A probe can observe the full world without becoming an application packet receiver. Tuning a display window does not retune the modem.

The current globe preset uses a 10 MS/s world centered at 2412 MHz. Alpha transmits at 2410 MHz and receives at 2414 MHz; Bravo reverses those frequencies. Both use matched 2 MHz OFDM/64QAM profiles, allowing simultaneous communication in opposite directions. Saved settings take precedence over the template, so the effective running state must be checked when revisiting an older scenario.

Other presets isolate specific effects: a nearby strong link, a directional-beam example, marginal range, a tuning mismatch, or a collision with two transmitters and one observer. These make it easier to attribute a failed decode to one controlled change.

Single-Carrier and OFDM Modems

Both waveform families support BPSK, QPSK, 8PSK, 16QAM, and 64QAM payload constellations. Single-carrier modulation uses symbol timing and RRC pulse shaping. The receiver applies matched filtering, timing selection, frequency/phase/gain correction, and residual phase tracking before nearest-symbol decisions.

OFDM replaces that symbol stream with data on many carriers. Acquisition identifies the packet, FFT processing estimates the channel, equalization corrects each carrier, and pilots support residual phase correction. The radio exposes guard allocation, DC nulling, pilots, prefix ratio, window rolloff, and payload scrambling.

An important implementation detail is that carrier count and sampled FFT length differ. With 64 carriers over a nominal 2 MHz grid, useful-symbol duration is 32 microseconds. At 2.5 MS/s, that is 80 samples; a 25% prefix adds 20 samples. At 10 MS/s, the same physical useful symbol has 320 samples and an 80-sample prefix. Raising the world rate does not by itself multiply the modem’s payload bitrate.

The sampled length is rounded from sample_rate × carrier_count / nominal_bandwidth. The configured carrier allocation determines which bins carry data, and the sampled band and receiver bandwidth must accommodate the occupied signal.

A profile fragment for both transmit and receive can look like this:

{
  "waveform": "OFDM",
  "modulation": "64QAM",
  "ofdm_fft_size": 64,
  "ofdm_bandwidth_mhz": 2.0,
  "ofdm_cyclic_prefix": 0.25,
  "ofdm_window_ratio": 0.02,
  "ofdm_guard_fraction": 0.1,
  "ofdm_null_dc": true,
  "ofdm_enable_pilots": true,
  "ofdm_pilot_spacing": 16
}

This is a radio profile fragment, not a complete scenario file. Matching transmit and receive modulation and waveform settings is essential to making a controlled channel test interpretable.

Channel Effects and Acquisition

A clean transmitted constellation does not imply a clean received one. The mixer derives path length and direction, applies transmit and receive antenna gain, adds delayed echoes, and combines signals with receiver thermal noise and self-interference settings.

An echo adds both a delayed contribution and a complex phase relationship. A cyclic prefix can accommodate delay spread, but it does not make a receiver immune to arbitrary noise, frequency offset, or incompatible settings. Similarly, stronger transmit power does not remove a collision between equal-power signals in the same channel.

The supplied long-distance globe scenario deliberately uses elevated simulated transmit power to make its visual demonstration work. Its geography and waveform settings should not be interpreted as a validated hardware link budget. Directional and marginal-range scenarios provide more focused ways to inspect gain and sensitivity behavior.

Retaining Captures on the GPU

The portable world uses WGFX, while native CUDA/HIP backends share their implementation. The accelerated receiver retains burst IQ in reusable device capture buffers. World-mixing frames append decodable receivers through GPU buffer copies, and packet acquisition reads the retained data directly.

That avoids downloading an entire capture and then uploading the same IQ for acquisition. Host-side tracking can receive acquired symbols; GPU tracking can return assembled bytes and corrected constellation points for final validation in the CPU worker pool. Monitor readback is a separate consumer: the production monitor downloads its selected radio or probe, rather than every receiver’s raw IQ.

Buffer lifetime matters as much as allocation count. Capture handles keep outstanding buffers from being reused until their owners release them. Transmit cache entries include a waveform generation identifier, because a reused allocation address and unchanged length do not prove the waveform contents are unchanged.

CPU work remains significant. Link geometry, transmit preparation, framing, CRC, and receive completion use shared workers. The pipeline commits decoded results in RF order and permits bounded look-ahead, so an apparently fast transmitter cannot accumulate unlimited work ahead of receiver processing.

What radiomon Shows

radiomon showing Alpha’s corrected OFDM/64QAM constellation, ideal targets, and decode status
radiomon showing Alpha’s corrected OFDM/64QAM constellation, ideal targets, and decode status

The native monitor has three complementary views:

View Useful question
Globe and links Where are radios and the observation probe, and which links are active?
Spectrum and waterfall Which sampled frequencies contain energy, and how does it change over time?
Corrected constellation What distortion remains after this receiver’s correction stages?

The constellation retains up to 65,536 corrected payload symbols per receiver, with ideal targets and age fading. It can include noisy payload observations from packets that fail CRC. Acquisition or header failure cannot produce those corrected payload points, so an empty constellation is different from a poor-looking acquired one.

EVM, frequency offset, and CRC status describe the latest decode, while the cloud may contain several packets. Pausing freezes the displayed history and statistics while reception continues. Retuning resets history to avoid combining incompatible profiles.

Constellation updates travel incrementally over a WebSocket, up to 30 Hz, independently of the slower world-state HTTP poll. An acknowledgement bounds each connection to one update in flight; reconnects recover bounded history. This prevents a slow viewer from generating an ever-growing visualization queue.

Measuring Capacity and Delivery Separately

Frame-processing capacity is not successful aggregate data throughput. A stress case with every radio transmitting simultaneously may complete its DSP work on time while collisions prevent useful packet delivery. File benchmarks, by contrast, include decode, reassembly, storage, and final verification.

The recorded native 64-radio tests on a Ryzen 9 7950X / Radeon RX 9070 XT host used 2.5 MS/s and 12,500-sample frames, a 5 ms simulated frame interval:

Recorded ROCm soak Duration Real-time ratio Frame discontinuities Receive drops
64 radios and probe 30.03 s 0.9994 0 0
Moving radios, directional antennas, multipath and RF impairments 15.03 s 0.9986 0 0

The recorded 64-radio results include memory, readback, queue, and timing data. They characterize those native workloads on that host; they are not measurements of every modem profile or successful 64-way file delivery. A faster-than-real-time uncapped benchmark and an approximately 1.0× paced run answer different questions.

Useful runtime indicators are generated MS/s, real-time ratio, scheduler lag, pending GPU/decode work, receive drops, and receiver completion. Watching only GUI FPS misses whether RF simulation time is falling behind.

Running a Controlled Experiment

After the tsim build, run these in separate terminals from the repository root:

./build/dist/bin/radios/iqradio \
  --scenario ./build/dist/bin/radios/scenarios/nearby.json \
  --state nearby_runtime.json --storage nearby_data \
  --api-port 33106 --monitor-iq "shm;none;iqradio-monitor" --backend auto
./build/dist/bin/radios/radiomon \
  --api-url http://127.0.0.1:33106 \
  --iq-listen "shm;none;iqradio-monitor"

Open the console at http://127.0.0.1:33106/, claim radios, and first verify a small text message and file. Then change one link condition and inspect spectrum, constellation, CRC, and receive progress together. Separate state/storage paths keep saved settings from one experiment from changing another.

The installed radio directory also has a Process Compose group. On Linux/macOS, RADIO_SCENARIO=collision process-compose -U from build/dist/bin/radios/ starts a collision demonstration. The default group uses port 33106 to coexist with Trackfuse on 33105; Windows groups use explicit supervisor ports and installed Git Bash launch commands.

The radio guide documents waveform controls and backend comparisons. Continue with RF synthesis and antennas, or return to the tsim overview.