Skip to content
Manic Microbes

Lineage

Attempt four, and the first one in Rust.

I'm David Bennell. I have been building some version of this since 2008, and the interesting part is not that I kept starting again — it is that I kept changing the same thing and watching what it did.

Where this came from

I got into this as a kid through CRobots and the various clones of it: you wrote a program for a little tank and watched it fight programs other people had written. Then I found DarwinBots, which was the same idea pointed at biology instead of tanks, and ended up helping maintain it.

After that I worked on DarwinBots 3.0, the C# and XNA rewrite, and then left to explore my own version — I wanted to try new avenues, and not be tied down to the way DarwinBots did things. That was 2008, and the thing I started has been called Manic Microbes ever since.

How many versions there have been depends on what you count. Most were substantially rewritten more than once along the way. Counting only the times I threw out the architecture and started from an empty directory, there have been three, and this is the fourth.

The first one is gone. It lived on an SVN server on a NAS array in my house, and I do not have the array any more — so everything below about 2008 is memory, and I have marked where it runs out. It was nearly twenty years ago.

What DarwinBots got right, and what I changed

Worth being specific, because if you came here from DarwinBots you are the person most likely to actually run this, and the differences matter more to you than to anybody else.

What DarwinBots got right is the thing everything since has kept: one shared world, genomes you write by hand, and no score anywhere. The competition is for energy and space, and whoever leaves more copies wins by definition rather than by adjudication. Nothing in fifteen years has improved on that.

Three things I changed, and each took a rewrite to get right. A genome here builds organelles rather than issuing commands, so an attack is not an instruction — it is an organ, and predation is what carrying one looks like from a distance. Matter is conserved to the exact integer, so a slide fills up because there is only so much body to go round rather than because a spawn rate says so. And a run is reproducible bit-for-bit from its scenario and seed, on any machine at any thread count, which makes a result something you can hand to somebody else.

The three before this one

2012 — the 3D one. Bots as spheres in an open volume, with a high-level DNA editor. Making it three-dimensional cost far more than it bought: everything got harder to see and harder to reason about, and I have stayed in two dimensions ever since.
2023 — the DOTS one. Back to 2D, with proper organelle slots and an entity-component architecture. Most of the current design got worked out here. The ECS lessons carried straight into the Rust version; the Unity dependency did not.

Something worth being straight about, because anyone who watches that second video to the end can check it. Near the finish it is running 30,957 cells at 125 frames a second, with sixteen diffusing chemicals and a fluid simulation underneath them — three chemicals short of what the current one carries, and near enough the same job. The target written down for the current version is fifty thousand at thirty, and it is not there yet.

Where it actually is today, before any of the work meant to make it fast: about 5,800 cells at 148 frames a second with the world running at 1×, and 13,700 at somewhere between 47 and 79 depending on which panel is open. A factor of four off, rather than an order of magnitude — and what the old build really tells me is that the number is not aspirational. I have been past it before, on a worse design.

I should also say plainly that the 2012 video promises free binaries for Windows, Mac and Linux. So did I, thirteen years ago. This is attempt four, and I would rather ship them than announce them.

Five attempts at a genome language

The thing I have really been experimenting with is not the physics. It is how a genome is written, and what that does to the path evolution takes.

One thing has never been up for negotiation. A genome has to survive an arbitrary random change and still produce a program that runs — every version had that as a core requirement, and every version had unit tests sitting on it. Robustness was never the variable. It was the floor.

2008C-like, infix, two passes

The most ambitious one, and the only one of the five that was not reverse Polish. Three stacks — one for data, one for operators and one for commands — running in two passes, one to interpret and one to execute. Commands were verbs the cell did directly: move, eat, attack. So a line read something like Move A + B / C, with the operator stack resolving the arithmetic in precedence order and handing the result up to the command. Brackets opened a recursive stack that resolved and popped its value back into the parent. (That fragment is from memory rather than quoted — the source is gone.) It was robust; I think an unbalanced bracket simply left the stack nested and never returned to the outer level, which is a behaviour and not an error. It went because it was confusing and clunky to work with, not because it broke.

2008–9Reverse Polish, verbose

A second language for the same engine, back to a stack machine much closer to DarwinBots but wordier on purpose — named variables, labels and tags, so a person could still read it without the two-pass machinery underneath.

2012Declarative rule sheet
found.target  = my.sensors.light.green > 100
reproduction  = my.energy > 400
propel.force  = my.energy * 0.3
attacking     = chasing and my.sensors.light.match

The high-level idea tried again, in 3D. Rules evaluated top to bottom every tick, with later lines masking earlier ones — condition-gated blocks before I had genes or promoters to call them that. Readable, and the most opinionated of the five about what a cell was for: most of the vocabulary is nouns I chose in advance.

2023Reverse Polish, flat-addressed
@?0  org.cilia build.at
@?4  420 store.val
@?8  rnd.tiny 5 sub store.val

Closer to this one than anything before it. Values were built up from parts rather than written whole, so a mutation could be partial. Organelles were reached by flat address — @36 meant slot 3, offset 6 — which is legal under any mutation and does not always mean what it meant: shift an organelle's base and the references to it quietly start reading somebody else's parameters instead.

NowByte-encoded assembly
GENE    #replicate
GLEN
SETLN
BUD
loop:
COPYB
LOOPLN  loop
SPLIT

No parse step, no identifiers, no addresses. Every byte is already an instruction, several byte values share each opcode so a great many point mutations change nothing, and a jump carries a pattern it searches for rather than a place it goes to. Cut the genome about however you like and the jumps still find what they were aimed at.

It is not a straight line, and I would rather say so. Twice I talked myself into a language a person could read — once in 2008 and again in 2012 — and twice I went back to a stack machine afterwards. The second time I had already done it once.

The distinction I was missing for most of that is between a mutation being legal and a mutation being meaningful. All five guarantee the first: hit a genome with a random byte and you get something that still runs. Only the current one is built for the second — that what comes back is likely to be a working relative of what went in, rather than an unrelated program that happens to execute.

Template addressing is what buys that, and it is the only real advance in fifteen years of doing this. Everything else on the list is a different way of spelling the same machine. The current language gives up almost everything I liked about the others to get it — no parse step, no identifiers, no addresses — and it is comfortably the least pleasant of the five to write by hand, which is why there is an assembler.

There is a second thing changing across that list, and I did not see it until I put them side by side. In 2008 the outermost thing on a line was a verb: move, eat, attack, with everything else an argument to it. There are no verbs left. A cell today cannot attack, because attacking is not something the language can say — it can only build a spike, and then the spike is a fact about the world rather than an action in a program.

Which is why the body kept getting rewritten alongside the language. They are the same question asked twice.

The body went the same way

The other thing that kept getting rewritten is what a cell is made of, and it followed the same curve for the same reason.

DarwinBots had you build things by name — shells, slime, venom, body mass — each with its own command for its own fixed effect. The 2012 version carried that habit into organelles: there were mitochondria and chloroplasts and vacuoles by then, but building one was still a command that made a set thing, wired to a fixed index. It worked, and it meant the catalogue was the engine’s business rather than the genome’s.

The DOTS version is where that turned over. The whole architecture became organelles-as-components: a cell was a set of slots, membrane and nucleus among them, and the genome had to fit inside the nucleus it had built. You could carry a bigger nucleus, or more of them, and store more code — and pay the upkeep for it every tick you were alive.

That last part is the piece worth keeping, and it is still how it works. Genome length is not capped by a rule anywhere. It is capped by the fact that somewhere in the cell there is a physical organ holding it, and that organ charges rent.

And so did telling friend from food

The third thing that keeps coming back is recognition, and I only noticed the pattern when I went back through the old versions.

The 2012 genomes identified prey by colour — green above a threshold, red and blue below it — and identified kin by broadcasting a frequency and comparing it against their own: found.kin = found.other and my.sensors.em.frequency == my.em.frequency. That is a public token, readable by anything nearby, which the engine reports and does not interpret.

Which is the badge, fifteen years early. What has changed is that 2012 wanted exact equality on a number nobody had a reason to fake, and the current version made it forgeable on purpose — which opens the half of the problem the old one closed off, that a lineage might learn to wear somebody else’s colours.

One thing the 2023 version could do that this one cannot

It had neurons. A cell could carry a layer of about ten of them and choose whether they ran in sequence or in parallel, and you wired them up yourself — including across cell boundaries, so a net was not a thing one cell owned. Ten cells joined together could carry a usefully large one.

Learning was the part I still think was right. There was no instant backpropagation: the signal stepped one place backward and one place forward per cell per turn, so a shorter wired route learned faster than a long one. How quickly a part of the animal could learn was a property of how its body was put together, which is exactly the sort of thing this project is supposed to get for free rather than declare. And it worked — it would learn to tell friend from foe, and to follow a gradient to food.

None of it is in the Rust version. I was not happy with how it was built and it needs rethinking from the start rather than porting, so it is not on the roadmap as a feature yet. It is here because a version of this that could learn within a lifetime is something I have already seen work, and I would rather say that than quietly leave it out.