PZ wire format 1

Photonic Zero

A megabyte,
carried by light.

Point a phone camera at this screen. The file crosses the gap on nothing but photons — no wifi, no bluetooth, no pairing, no upload.

A screen cannot hear you.

That single fact defines the whole design space. Show a sequence of frames to a camera and it will miss some — autofocus hunts, a hand shakes, the operating system schedules something else. There is no back channel. Nothing can ask for frame 47 again.

The obvious answer is to loop forever and hope. That is the coupon collector problem: slow, and unbounded in the worst case.

So it never asks.

Every frame is a droplet: the XOR of a pseudo-randomly chosen subset of the message's blocks. The receiver does not care which droplets it catches, only how many. Collect a little over the minimum and the message falls out.

Frames 0..K go out systematically, so a clean capture finishes at zero coding overhead and only pays for the fountain when frames are genuinely lost.

Drop a quarter of them.

The counters below are a real transfer running in this tab right now, losing one frame in four at random. Nothing is retransmitted and nothing restarts. Losing a quarter of the frames simply costs a quarter more of them.

frames emitted0
lost in flight0
reached decoder0
blocks recovered0 / 0

What a megabyte costs.

Measured on this build, not estimated. The bottleneck is the display refresh rate, not the decoder — which leaves plenty of headroom on a phone.

ProfileGridPer frameFrames / MBBest case
mono97²776 B1,35245 s
robust33²34 B30,84017 min
balanced49²479 B2,19073 s
resilient65²640 B1,63955 s
fast97²2,739 B38313 s

fast is the default for short, pre-encrypted or signed payloads. It needs a close, steady, well-lit colour capture. Choose mono when capture reliability matters more than speed; the decoder calibrates to whatever dark ink you pick.

For the fastest transfer, choose fast and 20 frames per second. That is 2,739 bytes per frame instead of 776 — about six times quicker than the default, at the cost of needing colour and a steadier hand.

Files are gzipped before transmission, which is worth roughly 2.5x on text, source and most binaries. The 1 MB sample here is random bytes, so it compresses by nothing at all — that is the honest worst case, and what the timings above assume.

Try it.

Open this page on a second device to receive. Nothing is uploaded — the file is read, encoded and decoded entirely inside the two browsers.

Send

This screen becomes the transmitter.

any colour dark enough to read

Fills the screen. Tap or press Escape to stop.
Hold the phone about a hand's width away and let it focus.

Receive

This device becomes the camera.

Camera off

Nothing is stored.

There is no backend. This page is static files on a CDN; your file is read into memory, encoded by a 100 kB WebAssembly module, and drawn to a canvas. It is never sent anywhere, because sending it somewhere would defeat the entire point of an air-gap protocol.

The decoded result lives in an object URL that is revoked the moment you leave, start another transfer, or hide the tab. Close this page and it is gone.

Take it apart.

Rust core with zero external dependencies, no_std, plus bindings for JavaScript, Python and C.

npm  install photonic-zero
pip  install photonic-zero
cargo add pz-core

PZ is a transport, not a security protocol. It provides no confidentiality and no authenticity — anything with a view of the screen can read the frames. Encrypt before you transmit if that matters. v0.1.0, wire format unfrozen, not independently audited.