Summary
| Measure | Result | Conditions |
|---|---|---|
| RaptorQ frames needed, real devices | Exactly K distinct symbols in every run | Device sessions 2 to 4 and all 64 replication runs |
| Best goodput, default size (version 27) | 30.5 KB/s | MacBook Pro to iPhone 12 Pro Max, one run |
| Best goodput, version 40 | 44.4 KB/s | iPad Air to iPhone 12 Pro Max, one run |
| Median goodput, version 27, Low Power Mode off | 22.6 to 25.3 KB/s | Three device pairings, 6 runs each |
| Median goodput, version 40, Low Power Mode off | 28.2 to 36.5 KB/s | Three device pairings, 4 to 6 runs each |
| Receiver decoder pass, best case | 23.8 ms median, 41.3 ms 95th percentile | iPhone 12 Pro Max, version 27 |
| Frames an uncoded sender would need instead | 3.4 to 5.5 × more | Closed-form estimate on measured loss |
A kilobyte here is 1,024 bytes, as the app reports it. Goodput is the transmitted object's size divided by the time from the first locked frame to completion.
Coding overhead
Synthetic
Measured with examples/rqbench.rs on an M1 Max (committed as docs/benchmarks/2026-08-07-rqbench-m1max.txt).
| Payload | Symbol | K | RaptorQ frames needed |
|---|---|---|---|
| 512 KiB | 1,445 bytes | 363 | 1.006 × K, with and without loss |
| 10 MiB | 2,933 bytes | 3,576 | 1.002 × K, with and without loss |
| 4,097 bytes | 512 bytes | 9 | 1.000 × K |
Decoding is not the bottleneck. RaptorQ decoded the 10 MiB case in 3.6 ms in total with no loss.
On real light
Every RaptorQ transfer recorded on physical devices completed at exactly K distinct symbols: 371 for K = 371 and 226 for K = 226 in session 2, 163 for K = 163 and 1,759 for K = 1,759 in session 3, 371 and 183 in session 4, and every one of the 64 replication runs. Duplicates, where a 30 fps camera reads a 24 fps display twice, cost decoder time but no coding overhead.
For history: an earlier LT-coded mode, since removed, needed 1.262 and 1.308 × K in the two device runs of session 1.
Throughput on devices
Session 4
MacBook Pro sending to an iPhone 12 Pro Max propped at a measured 30 cm, the same 521 KB image in each cell, one run per cell.
| Version 27, Low Power Mode on | Version 27, Low Power Mode off | Version 40, Low Power Mode off | |
|---|---|---|---|
| Goodput | 15.8 KB/s | 30.5 KB/s | 38.6 KB/s |
| Decode rate, run mean | 11.3 codes/s | 24.7 codes/s | 13.8 codes/s |
| Decoder pass after lock, median | 40.2 ms | 23.8 ms | 31.3 ms |
| Decoder pass after lock, 95th percentile | 102.1 ms | 41.3 ms | 72.4 ms |
| Captures never examined because the decoder was busy | 51 % | 16 % | 38 % |
| Time to lock | 4.46 s | 1.99 s | 0.94 s |
| Distinct symbols ÷ K | 1.000 | 1.000 | 1.000 |
| Thermal state | Nominal | Nominal | Nominal |
At version 27 with Low Power Mode off, the sender showed 24 unique frames a second and the receiver decoded 24.7 a second. The ceiling at 24 fps and 1,440-byte symbols is 33.8 KB/s, and the measured 30.5 is 90 percent of it. The rest is the two seconds of lock time and the remaining busy drops.
Replication: 64 runs on three device pairings
Each sitting ran four cells, four to six runs each, every run SHA-256 verified. Payloads were 310 to 318 KB. Values are the median over runs.
| Cell | MacBook Pro to iPhone 12 Pro Max | MacBook Pro to iPad Air (5th gen) | iPad Air to iPhone 12 Pro Max |
|---|---|---|---|
| Version 27 | 25.3 KB/s | 23.3 KB/s | 22.6 KB/s |
| Version 40 | 36.5 KB/s | 28.2 KB/s | 31.1 KB/s |
| Version 27, Low Power Mode on | 20.4 KB/s | 15.3 KB/s | 20.0 KB/s |
| Version 40, Low Power Mode on | 31.1 KB/s | 28.3 KB/s | 31.8 KB/s |
The contrasts that held in all three sittings:
| Contrast | Sitting 1 | Sitting 2 | Sitting 3 |
|---|---|---|---|
| Decoder pass, version 40 against 27 | 1.59 × | 1.59 × | 1.67 × |
| Decoder pass, Low Power Mode on against off, version 27 | 1.59 × | 1.34 × | 1.47 × |
| Decoder pass, Low Power Mode on against off, version 40 | 1.55 × | 1.41 × | 1.41 × |
| Goodput, version 40 against 27 | 1.44 × | 1.21 × | 1.38 × |
What this shows:
- Version 40 is faster, but not twice as fast. It carries about twice the payload per code, and the decoder takes about 1.6 times as long to read it.
- Low Power Mode slows decoding on every device measured. The size of the effect differs between devices.
- Thermal state was not the cause. No run recorded a thermal transition.
- No loss of decoding success at version 40 was detected in the replication, although a single earlier run had suggested one.
Acknowledgement
In the one recorded field run with auto-stop on, an iPad sending to an iPhone, the acknowledgement arrived in one round trip. The sender streamed 200.5 seconds for a receive that took 186.5, a tail of 1.075 ×. Without auto-stop, session 2's senders streamed 1.5 to 2.4 times longer than the receiver needed.
The grid
From a Mac sender, two codes per frame delivered 14.6 KB/s against 6.8 KB/s for a single code in an earlier session. That comparison crosses sessions, so it is reported but not relied on. From an iPhone sender the grid failed to transfer at all, which is why phones never render it.
Synthetic gauntlet
A seeded degradation harness (kit/Sources/AirKalonGauntlet) renders frames, degrades them under control and decodes them with Vision on an M1 Pro. It describes that machine, not a phone.
| Cell | Result |
|---|---|
| Gaussian blur | 50 % decode floor at 0.407 modules (version 27) and 0.406 modules (version 40) |
| Two frames blended in one capture | 50 % decode threshold at a 0.397 mix |
| Prefilter | Threshold 2.08; no changed frame ever classed as a repeat at any level tested |
| Coding comparison, 262,144 bytes at 2,953-byte frames | RaptorQ 90 captures for K = 90, against 132 for an earlier LT-coded mode, since removed |
| Detection cost | 11.5 ms floor at 1080p, 7.8 ms for each additional code |
Without a fountain code
The baseline every fixed-rate design reduces to is a sender that cycles the K blocks in order and a receiver that must catch every one. Using each session 4 run's measured share of shown frames that were read, such a sender would have had to show 3.4 to 5.5 times as many frames as the rateless sender needed. The penalty grows as the channel gets worse.
Sender cost
QR encoding
A stream's first frame scores all eight QR masks; every later frame reuses the chosen one. Measured with examples/rqbench.rs on the development Mac, as recorded in the repository README:
| Version | First frame, all eight masks | Later frames, mask pinned |
|---|---|---|
| 27 | 3.01 ms | 0.16 ms |
| 40 | 6.31 ms | 0.31 ms |
Pacing
The credit pacer was measured camera-free with PacerBenchTests on a mocked 60 Hz display link, against a strict-interval pacer that never catches up (docs/benchmarks/2026-08-14-sender-pacing-bench.md). At the measured version 40 production cost of 0.73 ms per frame, median:
| Condition | Credit pacer | Strict interval |
|---|---|---|
| Steady production | 24.03 fps | 20.00 fps |
| Jittery display-link ticks | 23.33 fps | 16.27 fps |
| Spiky production and jittery ticks | 21.57 fps | 16.23 fps |
This measures the sender's emission rate only, not what a camera receives.
Resource use on the Mac
Measured on an M1 Max with 1 Hz sampling (docs/benchmarks/2026-08-08-efficiency.md). The sending rows were measured while an earlier LT-coded mode, since removed, was the default, and at that time the acknowledgement watcher ran on every send; it is now off unless you switch on auto-stop.
| State | Average CPU |
|---|---|
| Idle, any tab | 0.00 to 0.05 % |
| Sending at 24 fps, single code | 61.1 % |
| Sending at 12 fps | 37.5 % |
| Receiving, searching for a code | 32.5 % |
The Mac app bundle measured 2.5 MB in the same pass.
How the numbers were measured
- Every device figure comes from the apps' own session reports, pasted verbatim into
docs/benchmarksand collected inkit/Benchmarks. - From session 4 onward the receiver times each decoder pass directly, split into before and after the optics lock, and records thermal state, Low Power Mode, capture format, locked exposure and the module size it sees.
- Comparisons are made only within one sitting. Absolute values are never compared across sessions, and a per-pass time is never inferred by dividing elapsed time by the number of passes. Two earlier conclusions were withdrawn under these rules.
- Replication intervals are percentile bootstraps over run values, with exact Mann-Whitney tests. The unit of analysis is the run, not the decoder pass.
What has not been measured
- No Android device and no device outside one vendor.
- No sustained-load or thermal soak run.
- Nine of the twelve cells in the session 4 protocol, including 720p capture and the alternative decoder paths.
- The 2× grid together with RaptorQ on a device.
- Handheld transfers under controlled conditions. The replication sittings held distance by hand.