Soup 4: the same 512 of its 131,072 programs at each moment, every program drawn as an 8 × 8 tile of bytes. Press play. For about 896 epochs the tiles are noise. At epoch 960 copiers appear and reach 25% of the sample, then fall back to 3.5%. At 1,216 they return and take over: from then on about 96% of these programs can copy themselves, yet almost no two of them are identical.
The setup
The world is a soup of 131,072 programs, each 64 random bytes. Every epoch the programs are shuffled into random pairs. Each pair is glued into one 128-byte tape and run as code for up to 8,192 steps, then cut back into two programs and returned to the soup. A little background mutation (0.024% of bytes per epoch) keeps things stirring.
There is no fitness function. Nothing scores the programs, nothing gets deleted, and nothing is rewarded for copying. The only way a pattern can spread is if running it happens to overwrite its partner with itself.
To spot the moment that happens, the paper measures high-order entropy: bits per byte by plain byte counting, minus bits per byte after compression. Random bytes score about zero, because they can’t be compressed. A soup full of copies scores high, because it’s varied byte by byte but extremely repetitive. I count a soup as alive once that number climbs above 2 bits and stays there for at least 512 epochs.
Meet a replicator
This is the most common program in soup 4 at the end of my run. It’s 64 bytes, and most of them are no-ops, shown as dots. It is typical of the crowd rather than a champion: the most any single program ever had was 384 exact copies out of 131,072. The soup is not a clone army. After the takeover about 96% of the programs I sampled could copy themselves, and nearly all of them were different.
The working part is a loop of four instructions with no-ops in between: [ < , } ]. Head 1 reads the replicator from its first byte forward. Head 0 starts at 0 and immediately wraps around to the last cell of the tape, so it writes into the partner from the end backward. Each pass copies one byte, and the loop keeps going as long as the copied byte isn’t zero. The replicator contains no zero bytes, so it never stops early.
The result is a mirror image. The partner ends up as the replicator written backwards. That mirror image carries a backwards copy of the same loop, which now reads forwards, so it replicates too, and what it makes is the original. It’s a two-step life cycle. In tests against 200 random partners the replicator made its mirror image 100% of the time and survived intact 100% of the time. It only works when it runs first on the tape, which happens in half of all pairings. That turns out to be plenty.
Try it below. Run it and watch head 0 walk backwards through the partner.
The outlined cell is the instruction pointer. The blue bar marks head 0 (read), the orange bar marks head 1 (write). Cells flash when they change.
Four soups, one trick
In every soup that came alive, the most common program was a mirror copier with the same two-step life cycle. Their code is different. They evolved separately, in soups that never touched. And across all four soups, I tested every sampled program and didn’t find a single one that makes a straight, unmirrored copy of itself.
Three of them run the same loop as soup 4: step head 0 back, copy with ,, step head 1 forward. Soup 8 found the exact opposite: head 0 reads forward and head 1 writes backward, copying with . instead. It’s the same idea with the two heads swapping jobs.
The soups didn’t end up the same, though. In soups 2, 3 and 4, more than 90% of sampled programs could copy themselves. In soup 8, even 2,048 epochs after its takeover, only 16% could; the rest mostly halt within a few hundred steps. Its copiers live among a lot of debris. And in soup 2, most copies come out as mirrors shifted around the ring: 92% of programs copy themselves, but only 3.5% make a perfect, unshifted mirror.
My guess at why mirrors win: both heads start at cell 0. Walking a head backwards lands it in the partner immediately, because the tape wraps around, so a mirror copy needs no setup. A straight copy would first have to move a head 64 cells to reach the start of the partner, which takes more code and is less likely to appear by accident.
The rules
Every byte is one instruction. Ten of the 256 byte values do something. The other 246 are no-ops, which is why a random program mostly does nothing.
| Byte | What it does |
|---|---|
| < > | Move head 0 left or right |
| { } | Move head 1 left or right |
| + - | Add or subtract 1 at head 0 |
| . | Copy the byte at head 0 to head 1 |
| , | Copy the byte at head 1 to head 0 |
| [ | If the byte at head 0 is zero, jump past the matching ] |
| ] | If the byte at head 0 is not zero, jump back to the matching [ |
Code and data share the same 128-byte tape, so a program can rewrite itself and its partner while it runs. Both heads start at 0 and wrap around the tape. Execution stops at the end of the tape, at an unmatched bracket, or after 8,192 steps.
How I ran it
- A 140-line C simulator following the paper’s setup: 217 programs of 64 bytes, random pairing each epoch, an 8,192-step limit per pairing, 0.024% background mutation, and brotli at quality 2 as the compressor. Eight soups ran side by side on this laptop’s 8 cores.
- I stopped each soup at 6,144 epochs, well short of the 16,384-epoch horizon the paper used, because a soup full of replicators is slow to simulate. The paper reports that 40% of runs come alive within 16,384 epochs. Here 4 of 8 did so within 6,144, a faster rate than the paper’s numbers suggest. Eight soups is a small sample, and my simulator may differ from theirs in small ways (for example, in exactly how mutation is applied), so I wouldn’t read much into the difference.
- Every run is deterministic given its seed. I re-ran soup 4 with a snapshot every 64 epochs, and it reproduced the original run exactly.
- The “alive” threshold (above 2 bits for 512 epochs) is my own choice. My first version counted the first crossing as the takeover, which mistook soup 2’s short-lived flare for the real thing. I changed the rule and re-ran that soup.
- A program counts as a copier if, paired first with a random partner, it leaves the partner as a copy of itself (straight or mirrored, possibly shifted around the ring) with at most 8 of its 64 bytes wrong. I tested every program in each 512-program sample.
- The viewer shows 512 of the 131,072 programs, always the same 512 slots.