I wrote one program of 55 instructions that copies itself. Then I wrote nothing more. Everything else in this report the soup came up with on its own: a replicator half the length of mine, parasites that steal a neighbour’s copy loop, and ecosystems that eat themselves to extinction.
In most artificial-life models “reproduce” is a built-in command, so evolution only tunes the parameters of a mechanism that already works. Not here: copying yourself is not a primitive but a program, which the organism executes one instruction at a time. It can therefore be lost, broken, shortened — or stolen from a neighbour.
The set holds exactly 32 instructions — 25, five bits. There are no gaps in the opcode space: any mutation of any bit yields another valid instruction. This language has no syntax errors, which is precisely what makes it evolvable.
Addressing goes by landmark rather than by address: a jump carries a trailing run of nop0/nop1, and the machine searches outward for the complementary run. A mutant that has shifted, shrunk or grown still finds its own markers. And — this is what decides everything — it can find somebody else’s.
An organism may read and execute anywhere in the soup, but may write only inside its own body and inside the block it has reserved for a daughter. That is the single asymmetry in the whole model, and it is deliberate: sandbox execution too and the soup becomes safe — and parasitism becomes impossible by construction. Then there is simply no result to find.
With mutation on, every bug in the machine reads as “evolution”. So check.py runs the ancestor with mutation off and demands: all five landmark searches resolve exactly where intended; the daughter is byte-identical to her mother; the daughter is herself fertile; the soup fills to the reaper’s threshold and stays clonal. Only then is mutation switched on.
The pressure behind that compression I found and then verified with a separate run. Every organism gets a constant slice of CPU time regardless of genome length, so a shorter genome finishes copying in fewer slices and reproduces more often. Make the slice proportional to genome size and the pressure disappears: in the prop run genomes sit at 48–62 instructions for all 400 million instructions. Shrinking is not a property of life in general but a consequence of one scheduling rule.
“Parasites evolved” is too pleasant a conclusion to accept by eye. Every candidate is run in three tubes with mutation off, and the test of reproduction is strict: only a byte-identical daughter counts. Calling divide over a block of garbage is not reproduction.
| genotype | length | peak | births | verdict | alone | clonal host | community | foreign |
|---|
A separate finding turned up along the way: some parasites fail even in a clonal lawn of hosts and come alive only in the reconstructed community. Their short landmarks are tuned to a particular geometry of neighbours, and a monoculture does not provide it. The parasite is adapted to a neighbourhood, not to a host.
The soups that died share one sequence: foreign execution climbs, mean genome length falls to the 8-instruction floor, and against that background the population collapses from four hundred organisms to one within a few million instructions. Parasites crowd out the carriers of the copy procedure — and are left without one. In the soups that survived, foreign execution stays between 0.02 and 0.13.
Another defect in the model I found the same way — from the data, not from intuition. In the first batch of runs the soup froze: 37 organisms that do not reproduce and therefore never fill memory; and with memory unfilled the reaper never runs and nobody dies. Half a billion instructions in which nothing happens. That forced a starvation rule: an organism that has burned far more time than copying needs and has produced no daughter dies. After it, freezing gave way to honest extinction.
Python 3 standard library only, no dependencies. The soup runs at about a million instructions per second on one core.
python3 check.py # prove the ancestor is correct python3 run.py --tag mine --seed 7 # evolve python3 analyze.py mine # rule on what evolved # the parameters that matter --soup 30000 # soup size in cells --copy-mut 0.001 # chance of a flaw per copied instruction --cosmic 20000 # one random bit flip in memory per this many instructions --slice 25 # CPU slice per organism --slice-prop # make the slice proportional to genome length --idle 60 # starvation threshold (0 disables it)