The Same Two Hundred Years, Twice
Replay is a volume finding
This volume has spent twelve chapters producing findings: a population that climbs for about fifteen years and then sits within a few animals of three hundred and thirty-six, one species that splits into several, and archived genes that drift when nothing reads them. Replay needs the same initial state, inputs and deterministic execution rules in the same reference environment.
A deterministic branch may consume a different number of draws for different outcomes and still reproduce exactly. This volume also fixes draw counts for controlled comparisons between branches, so a mutation rate change can be measured without moving every later roll.
A finding from a run you cannot get back is a story. The page needs more: the same seed, same years and same code producing the same archive, even after the valley has spent two hundred years making births, deaths, species and wiring changes.
Four-year rehearsals catch obvious mistakes, but the real failure can hide for a hundred thousand asks. The volume's claims came from two centuries, so the replay proof has to run two centuries too.
The chapter collects the determinism rules already in force, runs the two long worlds beside each other, names the tick where an intentionally broken run parts, and prices ten copies of the valley. The proof is the archive bytes, not the absence of a panic.
Every valley on this page is the one the channels, speciation and archive chapters ran: sixteen by twelve cells with the rim cracked and walled, the room rule on, twenty-five animals founded on stream 12 with a founding wiring of a hundred and forty-four links, the eight ground channels open and billed, children sorted into species as they are born, and every birth and death written to an archive. The four-year runs and the two-century run are the same valley stopped at different places, and the four grounds at the end of the page are that recipe on more ground.
Nothing here may be set beside a figure from a chapter before the wiring could grow. Those runs evolved a flat row of three hundred and seventy-eight weights and spent a different count of numbers on every birth, so they are a different history of the same seed.
Determinism rules already in force
Collect them first, because there is nothing new to write. Each of these was settled on the page that needed it, and every one was settled the same way: by taking something that could depend on an outcome and making it depend on nothing at all.
A draw count never depends on what a draw said. The copying chapter takes
two numbers off stream 16 for every gene of a child, always: the first decides whether the
gene moves and the second is its new value, and the second is taken whether or not it is
used. A mutation pass over a genome of G genes is exactly 2G
numbers. The crossing chapter takes 1 + G off stream 17 in the same spirit,
one for whether the birth has two parents and one a gene, and the per-gene draws happen on
a single-parent birth too. These are comparison rules, stronger than replay alone
requires. The graph chapter takes exactly four off stream 19 every birth,
for add a link, where it goes, add a node and which link is split, mapped onto the
candidate list by index. And the birth chapter puts all of that together into one rule
with no exceptions in it: a creature that is asked for a child spends
8 + 3G numbers whether the child is born, refused for want of a store or
refused for want of ground.
Numbers that will make no difference are drawn anyway. The arena's elite place holds its tournament and runs its whole mutation pass and uses neither, so a run with one champion carried through and a run with none read the same streams in the same order and differ only in which genome sits in slot 0. Without that coupling, the comparison would also include a shift in which random draws reach later slots.
These comparison benches choose an index with
int(s.Float64() * n). That spends one draw, at the cost of a
small mapping bias when the finite set of floating-point samples cannot divide evenly
among the candidates. s.IntN(n) avoids that bias with rejection sampling
when needed; its draw count can vary. With the same seeded generator and deterministic
call order it still reproduces. The mapping here stays as it is to hold these experiments' draw alignment and their tapes,
and not because rejection is nondeterministic: it is not.
Arithmetic happens in a written-down order. A node in a grown controller adds its incoming links ascending by source node number, which is the only order that reproduces the fixed network's row of multiply-adds. The graph chapter turns that off behind a flag and 4,825 of 6,000 output numbers move, none of them by more than three parts in ten thousand million million. Floating-point addition is not associative, so a total is a fact about an order as much as about a set of numbers.
Work is evaluated in a written-down order, and work that draws nothing may be split. The arena scores its genomes in population order, and scoring a genome draws no random number at all. That is what licenses handing the scoring to several goroutines: the generation's digest is the same on one, two, four, eight and sixteen of them. In the valley the same argument runs the other way round, and the phase order is the answer to it: build the view, cast every fan, step the roster in order, bury the dead, mint the births. A newborn goes on the end of the roster and is not stepped on the tick it is born, because the view every creature in that phase is reading was built before the child existed.
The cheap question is asked before the expensive one. The long-run chapter asks the ground whether a creature has anywhere to put a child before the birth touches a generator, and its counterfactual flag asks after. Eight years of the same valley then give 898 births instead of 917, which is a different valley and not the same one running slower. The reason is the rule at the top of this section arriving from the other side. A creature that cannot breed has to cost the pass nothing, because the moment it costs a draw it has moved the sequence for everybody behind it.
What is missing is any way to notice. Every rule above is enforced by somebody having written the code that way, and a valley whose breeding pass has been reordered is a valley that still ticks, still closes its grams to the last bits of the adding, still fills up and levels off. It needs an instrument, and the instrument is a tape.
// fold writes one tick of this world into the run's hash: the tick, every
// standing plant, every living creature in roster order and the five
// counters the pass keeps.
//
// The numbers go into one scratch buffer that is emptied and refilled
// rather than allocated, and the whole buffer is handed to the hash in
// one call. A tape that allocated a slice a tick would be the most
// expensive thing in the phase and would be measuring itself.
func (w *run) fold() {
// The tick that has just run, which is one less than the tick the
// valley is now standing at: terra.Valley.Tick calls the phase and
// then moves the clock. Numbering the line for the phase it holds is
// what lets a tape line and a line of the pool's log name the same
// tick.
now := w.v.Now - 1
b := w.buf[:0]
b = num(b, uint64(now))
for _, st := range w.v.Stands {
b = num(b, uint64(st.At.Y*w.v.Grid.W+st.At.X))
b = num(b, math.Float64bits(st.Plant.Mass))
}
b = w.herd(b)
b = num(b, uint64(w.r.Gone))
b = num(b, uint64(w.r.Steps))
b = num(b, uint64(w.p.Made))
b = num(b, uint64(w.p.Draws()))
w.buf = b
w.h.Write(b)
if w.Fine {
w.line("tick %7d alive %5d born %6d draws %13d %s\n",
now, len(w.r.Live), w.p.Made, w.p.Draws(), w.At())
}
}
func body(b []byte, c *beast.Beast) []byte {
b = num(b, math.Float64bits(c.Pos.X))
b = num(b, math.Float64bits(c.Pos.Y))
b = num(b, math.Float64bits(c.Face))
b = num(b, math.Float64bits(c.Store))
return num(b, math.Float64bits(c.Ate))
}
// num puts one number on the end of the scratch buffer, biggest byte
// first, so a tape written on one machine reads the same on another.
func num(b []byte, u uint64) []byte {
return binary.BigEndian.AppendUint64(b, u)
}
// At is the sixteen characters this run has folded so far: every tick it
// has taken, in the order it took them.
func (w *run) At() string { return fmt.Sprintf("%x", w.h.Sum(nil)[:8]) }
The one call not printed there is herd, which puts every living creature
into the buffer in roster order. It is three lines long, it is the most obvious code on
this page, and it is the whole of the worked failure a few pages down.
Four decisions in the rest of it, and each of them is the difference between an
instrument and a decoration. The hash is fed once and never reset, so the sixteen characters at year 200
depend on all 719,550 ticks and not on the last one. Creatures go in in roster order,
which is an order the run already has. Every float goes in as the bits it actually is,
through math.Float64bits, because printing a float to six places would hide
exactly the last-bit disagreements a reordered sum produces. And the numbers go in
biggest byte first, so the tape is a property of the run and not of the processor it ran
on.
The scratch buffer is the small idea. w.buf[:0] keeps the array and throws
away the length, so a tick of a valley with three hundred and thirty-six animals in it
refills about eighteen kilobytes of memory the last tick already had. Doing this with a fresh
slice would put an allocation on the hottest path in the program, and a measurement that
slows the thing it measures is measuring something else.
With that, the rules stop being claims. Take one short run of the valley, four years of it, and change one thing about it at a time. Five of those changes are ones the rules above say cannot be noticed: writing the archive or not writing it, keeping the species register or not keeping it, and casting the fans on two, eight or sixteen goroutines instead of one. One of them is a rule broken on purpose, and it is the least suspicious-looking change in the list.
$ go run ./cmd/twice -mode holds -years 4
twice: 16x12 valley, 4 years, one change a row
every row founds the same ground off seed 5, scatters the same herd on
stream 12 and breeds it through the same pass; the digest folds every tick
of the run, and draws is every number the pass took off streams 16 to 20
the run, with the rules digest draws the tape
nothing changed: the run this volume ships the same 88850053ac0a0767 916356 unmoved
no archive written the same 88850053ac0a0767 916356 unmoved
no species register kept the same 88850053ac0a0767 916356 unmoved
the fans cast on 2 goroutines the same 88850053ac0a0767 916356 unmoved
the fans cast on 8 goroutines the same 88850053ac0a0767 916356 unmoved
the fans cast on 16 goroutines the same 88850053ac0a0767 916356 unmoved
the roster walked from the end moved 19229c17411233b9 182222 MOVED
every row read what the rules say it must: five changes to how the run is
written down or carried out, none of them visible in the tape, and one
change to the order the pass walks the roster, which is a different world
The six agreeing rows carry three separate arguments. The archive spends no number off any stream, so a run that keeps a sixteen-megabyte history of itself and a run that keeps none are the same run. The species register spends none either, and on this ground the compatibility threshold never once refuses anybody, so sorting the valley into species changes who breeds with whom exactly nowhere. And the fan casting goes wide because a fan reads ground that cannot move inside a tick and writes into a slice belonging to one animal, so sixteen goroutines do the same work as one in whatever order they like and the digest cannot tell.
The last row is the one to sit with. Walking the roster from the end asks the same creatures, off the same streams, in the same fixed order inside each birth. It is not skipping anybody and it is not drawing anything extra. And it is a different world.
What the draws column misses
The draws column is the first instinct anybody has about a seeded system, and
the table shows both what it is good for and where it stops. Six rows spent 916,356 numbers
and the seventh spent 182,222, so on this particular run the column happens to shout. Look
at what it is actually counting, though: 916,356 numbers over four years is the sum of what
every ask cost, and an ask costs 509 numbers whatever happens. The column can tell you how
many creatures were asked. It cannot tell you which creatures they were, or in what order,
or which of them got the first number off the mate stream.
A run where the order moved and the number of asks happened to stay the same would leave that column unmoved, and there is one on the next page: on the tick these two worlds first part, both of them ask four creatures and both spend the same 2,036 numbers. A ledger of how much was drawn is a check on the draw counts, and the draw counts were never the thing in most danger. The tape is a check on the run.
The twin 200-year run
A four-year run is a rehearsal. The findings this volume is actually making came out of two centuries, and two centuries is where the ways of getting this wrong hide: a divergence that needs a hundred thousand asks to show up, a counter that goes round in year 140, a lineage that only drifts past the compatibility threshold late. So run the whole thing, and run it twice.
The volume's run script uses a version-tagged Go image:
podman run --rm --network none -v "$PWD":/src:z -w /src
docker.io/library/golang:1.26 go run ./cmd/twice -mode twice -years 200.
For most chapters that is housekeeping. Here it is part of the argument. A seed fixes
what the generators hand out; it fixes nothing about the compiler that turned this
package into instructions, the standard library the hash and the sorting came from, or
the version of Go whose map iteration the failure below depends on.
golang:1.26 is mutable, so it does not pin those versions permanently.
For a reference capture, resolve and record an image digest, select
--platform linux/amd64, and record go version.
Fixed arithmetic order is necessary here but does not establish floating-point
bit equality across architectures or compiler versions. The original early-volume
image digest was not recorded; the contract compatibility note distinguishes that
missing provenance from the later digest-pinned stack.
man podman-run for the flags, and
--network none because a run that reaches the network is a run that
depends on the weather somewhere else.
The two runs go side by side on two goroutines, and that is a claim as well as a convenience. Nothing in a run of this valley is shared with anything outside it: the ground, the herd, the pool, the five generators, the species register, the archive and the tape all hang off one value, and no package this volume touches keeps a variable anybody writes to. If that were false, two of them ticking at once would find out.
names := []string{"twice-a", "twice-b"}[:runs]
pair := make([]*run, len(names))
var wg sync.WaitGroup
for i, name := range names {
at := w
at.Name, at.Tape, at.Keep, at.Sort = name, true, true, true
wg.Add(1)
go func(i int, r run) { defer wg.Done(); pair[i] = r.live(years) }(i, at)
}
wg.Wait()
at := w is doing real work in there. run is a struct held by
value, so each goroutine is handed its own copy of the settings and then fills in its own
valley, roster, pool and hash on top of them, and no two of them share a pointer to
anything. The two names are the other half: a run owns its files, and two runs writing to
one tape would interleave their lines and produce a file that matched nothing. Every file
this bench writes is named for the run that wrote it, and that is not tidiness. The gate
that checks this book runs a whole volume's quoted commands in parallel in one working
directory.
$ go run ./cmd/twice -mode twice -years 200 -every 20
twice: 16x12 valley, 200 years, run twice at once off seed 5
each run founds its own ground, scatters its own herd on stream 12,
writes a line a year to its own tape and every birth and death to its
own archive; the two runs share no memory and never look at each other
run a run b
creatures founded 25 25
born 1725 1725
struck off 1414 1414
still walking 336 336
species opened 1 1
creatures asked for a child 154473 154473
numbers the pass spent 79103308 79103308
links added 1879 1879
nodes put in 3126 3126
grams minted into newborns 29693.758061 29693.758061
genomes the pool holds 1750 1750
numbers in all of them 294016 294016
records archived 3164 3164
the whole run, folded ad43e40c47815685 ad43e40c47815685
the two tapes, 200 lines each: one line a year, the digest of every tick
taken up to the end of it
twice-a.tape 20400 bytes sha256 558dc98c09630cbf
twice-b.tape 20400 bytes sha256 558dc98c09630cbf
cmp IDENTICAL
the two archives: every birth and every death, in the order they happened
twice-a.jsonl 16049646 bytes sha256 577e17a1978bd5db
twice-b.jsonl 16049646 bytes sha256 577e17a1978bd5db
cmp IDENTICAL
run a's tape, one line in every 20
year 1 tick 4050 alive 9 born 14 gone 30 draws 26983 ea2c848aac459b99
year 20 tick 72450 alive 333 born 1091 gone 783 draws 45659395 dd61100260b6d4af
year 40 tick 144450 alive 333 born 1696 gone 1388 draws 78116885 5128c1329fdfadb2
year 60 tick 216450 alive 336 born 1718 gone 1407 draws 79073574 402c538dea5f75b3
year 80 tick 288450 alive 335 born 1718 gone 1408 draws 79073574 066bf69c18c063cf
year 100 tick 360450 alive 336 born 1720 gone 1409 draws 79077164 a33cb7a5c6cfb947
year 120 tick 432450 alive 337 born 1722 gone 1410 draws 79079210 a240911e74a57332
year 140 tick 504450 alive 336 born 1724 gone 1413 draws 79099206 417e6fa040bcbf25
year 160 tick 576450 alive 336 born 1724 gone 1413 draws 79099206 515773899455cb26
year 180 tick 648450 alive 336 born 1724 gone 1413 draws 79099206 47055869977557e1
year 200 tick 720450 alive 336 born 1725 gone 1414 draws 79103308 ad43e40c47815685
719550 ticks of valley each, and the two of them ran side by side
1439100 ticks in 7m51.721s, 3051 ticks a second (measured here; yours will differ)
Every column agrees, and the two lines under them are the claim this chapter exists to make. The tapes are twenty thousand four hundred bytes apiece and the same twenty thousand four hundred bytes. The archives are 16,049,646 bytes apiece, three thousand one hundred and sixty-four records of every birth and every death with the whole genome on each born record, and they are the same file. Two hundred years of a world where animals inherit, mutate, cross, grow connections and starve, run twice, and there is nothing anywhere in either of them to tell the two apart.
The tape excerpt says what the run did with the time. Nine animals at the end of the first
year out of twenty-five stood up; 333 by the twentieth and never far from that again. The
born column reaches 1,091 in the twentieth year, 1,696 in the fortieth and
1,725 in the two hundredth: the valley made nearly all the animals it was ever going to
make in its first forty years and then made twenty-nine more in a hundred and sixty. Look
at the draws column beside it and the reason is plain. It climbs to
78,116,885 by year 40 and finishes at 79,103,308, so the breeding pass is still asking,
still spending its 509 numbers an ask, and still being told there is nowhere to put a
child. Nothing has stopped happening. It has stopped succeeding.
Two things deserve a moment before moving on. The first is that this valley is not new.
The speciation chapter walked out of the same two centuries with the same 25 founded,
1,725 born, 1,414 struck off, 336 walking and one species, and the archive chapter wrote
the same run to a file of 16,049,646 bytes whose sha256 opens 577e17a1.
Three benches, written on three different pages for three different arguments, agree with
each other down to the sixteen characters at the end of a file. That is what a seed is
for, and it is the last thing any of those three pages would have noticed on its own.
The second is the cost, because it is the reason this page can exist at all. The pair took
seven minutes and fifty-two seconds here for 1,439,100 ticks of valley. The same bench
with -runs 1 takes seven minutes and twenty-six on the same machine for half
the ticks, and folds to the same ad43e40c47815685. Both of those are an eight-core Ryzen 7 3700X
talking, and yours will differ, but the ratio is the point: the second valley goes
on a core that was sitting idle and costs six per cent more wall clock than the first.
Doing a thing twice so you can find out whether it means anything is usually sold as
doubling the bill. Here it is a rounding error.
The first version of this tape did not fold creatures off the roster. It folded them out of a map from an identity to a creature, because that map was already sitting there: it is the obvious thing to build when what you want is to look an animal up by number, and once it exists, ranging over it is one line shorter than ranging over the roster and reading the identity off each body.
// herd puts every living creature into the tape: its identity and the
// five numbers a tick can move, in roster order, which is an order the
// run already keeps and never has to invent.
func (w *run) herd(b []byte) []byte {
if w.Loose {
return w.mapped(b)
}
for _, c := range w.r.Live {
b = num(b, uint64(c.ID))
b = body(b, c)
}
return b
}
// mapped is the worked failure: the same creatures taken out of a map
// built for convenience instead of off the roster. It is behind a flag
// so what it does can be run rather than described, and it is off in
// every run this bench ships.
func (w *run) mapped(b []byte) []byte {
byID := make(map[sim.EntityID]*beast.Beast, len(w.r.Live))
for _, c := range w.r.Live {
byID[c.ID] = c
}
for id, c := range byID {
b = num(b, uint64(id))
b = body(b, c)
}
return b
}
The two loops put exactly the same creatures in with exactly the same numbers. Here is what one world folded both ways comes to, over five runs of it.
$ go run ./cmd/twice -mode loose -years 4 -times 5
twice: the same world folded two ways, 5 times over
nothing about the valley changes between these rows: same ground, same
herd off seed 5, same pass, same 4 years. The left column folds the
tape off the roster, in roster order. The right column folds it off a
map from an identity to a creature, built fresh every tick
run folded off the roster agrees off a map: agrees
1 88850053ac0a0767 - -
2 88850053ac0a0767 yes no
3 88850053ac0a0767 yes no
4 88850053ac0a0767 yes no
5 88850053ac0a0767 yes no
5 runs of one world folded off the roster, 1 digest between them
5 runs of the same world folded off a map, 5 digests between them
Start from the column that behaves strangely, because it is the one carrying the diagnosis. Five runs of one seed and five different answers, and a sixth run gives a sixth. An evolutionary run that had genuinely gone nondeterministic would not do that. A race between goroutines gives a handful of answers with one of them common; an uninitialised value gives you the same wrong answer most days; a run reading the clock gives you something that drifts with the time of day. Five for five, every time, is something that is being randomised on purpose.
And the other columns say where it is not. The left-hand fold of those same five runs agrees with itself, so the valleys are identical valleys: same births, same deaths, same stores, tick for tick. Nothing about the world moved. What moved is the order the instrument read the world in. Go randomises the starting point of every range over a map deliberately, so that nobody writes code that quietly depends on an order the language never promised, and this tape depended on it about two hundred times a second.
The repair is the three-line loop, and the rule under it is worth more than the repair: a check has to be at least as ordered as the thing it is checking. The roster is a list, and this run has been careful about its order since the first creature was stood up on it. The map was never ordered at all, and a fold over it was asking a question the run has no answer to.
First divergence tick
Now break one of them, on purpose, and go and find the damage. The rule to break is the one the last figure highlighted, because it is the one with the least defending it. No test asserts it and no assertion checks it: the whole of its enforcement is a sentence in a doc comment, the birth chapter's own, saying that the roster is walked once, in roster order, at most one child per parent per tick. The first clause and the last would break something a reader could see. The middle one would not.
// Backward is the breeding pass walking the roster from the end
// instead of the beginning. It asks the same creatures, spends the
// same numbers off the same streams and never once looks at a
// creature it was not going to look at; all it changes is which of
// them reaches the front of the queue. It is kept behind a flag so
// what a reordered pass does to a run can be run instead of
// described, and it is false in every pool this book ships.
Backward bool
Three lines inside Breed read it, and the walk the birth chapter published
is the same walk with k counting and i naming the creature.
for k := 0; k < n; k++ {
i := k
if p.Backward {
i = n - 1 - k
}
b := live[i]
Everything downstream of that line is untouched. The threshold is read first, so a creature that cannot pay still costs the pass nothing. The ground is asked before any generator is. The five draws happen in the order 18, 17, 16, 19, 20. The count of numbers an ask spends is the same count. This is a reordering and nothing else, of the kind that arrives in a commit called "iterate backwards to allow removal during the walk".
The hunt has two passes in it, and the two-pass structure is the content. A tape with a line a year is two hundred lines for two centuries, cheap enough to write on every run, and it narrows a divergence from seven hundred and twenty thousand ticks to three thousand six hundred. A tape with a line a tick names the tick and costs a file of seven hundred thousand lines, which is not something to leave switched on. So the coarse tape says how far you have to run, and then you run that far again with the fine one.
$ go run ./cmd/twice -mode hunt -years 4
twice: one ordering rule broken, and the tick the two runs part on
16x12 valley, seed 5, 4 years, and one difference: run b's breeding
pass walks the roster from the end instead of the beginning. It asks the
same creatures, spends the same numbers off the same streams, and changes
nothing but which of them is asked first
pass one: a line a year
the tapes agree on 0 lines and part on line 1
a: year 1 tick 4050 alive 9 born 14 gone 30 draws 26983 ea2c848aac459b99
b: year 1 tick 4050 alive 10 born 15 gone 30 draws 61080 3a9de92200f99a50
so the difference is somewhere inside 3600 ticks, and a year tape
cannot say where. Run both again with a line a tick
pass two: a line a tick, over the 1 years up to it
the tapes agree on 603 lines and part on line 604
a: tick 1504 alive 22 born 1 draws 3563 8afe8f97266dee4e
b: tick 1504 alive 21 born 0 draws 3563 d0eaf1ecdff69de8
what the breeding pass did on tick 1504, in each run
run asked parent mate child grams the price the store
a 1 2 23 26 40.0000 360.0000 360.0600 born
a 2 19 0 0 40.0000 360.0000 360.0600 nowhere to put it
a 3 22 23 0 40.0000 360.0000 360.0600 nowhere to put it
a 4 23 22 0 40.0000 356.8231 360.0600 nowhere to put it
b 1 23 22 0 40.0000 360.0000 360.0600 nowhere to put it
b 2 22 23 0 40.0000 360.0000 360.0600 nowhere to put it
b 3 19 2 0 40.0000 360.0000 360.0600 nowhere to put it
b 4 2 19 0 40.0000 356.8231 360.0600 nowhere to put it
and what the two worlds came to after 4 years
a: in order b: from the end
born 74 83
struck off 84 89
still walking 15 19
creatures asked for a child 1800 358
numbers the pass spent 916356 182222
species opened 1 1
the whole run, folded 88850053ac0a0767 19229c17411233b9
Tick 1,504, and the six hundred and three ticks before it are identical in both worlds down to the last bit of every store. Both passes then ask exactly four creatures, and they are the same four: 2, 19, 22 and 23, the animals whose stores have just closed over 360.0600 with an open cell somewhere in reach. Both spend 2,036 numbers doing it, four asks at 509 apiece. The two blocks hold the same identities in opposite order and nothing else about them differs.
Read the price column down each block before anything else, because it settles what is going on. Ask one, ask two and ask three each describe a child costing exactly 360.0000, and ask four describes one costing 356.8231, in both runs. The price is a property of the place in the queue and not of the animal: the fourth ask of the tick takes the fourth block of numbers off the mutation stream wherever it happens to be standing, and that block moved one of the factors the price is worked out from far enough to take 3.1769 off it. In one run the animal holding the fourth place is creature 23. In the other it is creature 2.
Which leaves the one row in eight that says born. Nobody here fails to pay;
every store covers every price. Seven of the eight children are refused because the two
placement numbers named a cell with a body already on it, and the eighth is not.
Creature 2 is asked first in the forward run, so it draws the first angle and the first
distance of the tick, and from where it is standing that pair of numbers points at open
ground. In the backward run those same two numbers go to creature 23, which is standing
somewhere else, and they point at a cell that is taken. Creature 2 gets the fourth pair
instead, and so does everybody else, and nothing is born at all. Look at the mate column
on the way past: creature 2 is paired with 23 in one run and with 19 in the other, off
one number indexed onto its list of eligible neighbours.
After that the two valleys are different places. By the end of the fourth year one has made 74 animals and the other 83, one is asking 1,800 creatures for children and the other 358, and there is no sense in which either is the correct answer. The seed is the same. The ground is the same. The rules of the world are the same to the decimal. What differs is which of four animals was reached first on one tick in the first year.
Four tests hold the two halves of that down, and they run in a tenth of a second on a plate of ground with twenty-four animals on it, which matters: a claim that needs eight minutes of valley to check is a claim nobody checks.
$ go test -count=1 ./internal/gene/ -run 'OneSeedFolds|WalkingTheRosterFromTheEnd|OneOrderedPhase|APoolCounts' -v
=== RUN TestOneSeedFoldsToOneNumberHoweverOftenItIsRun
--- PASS: TestOneSeedFoldsToOneNumberHoweverOftenItIsRun (0.05s)
=== RUN TestWalkingTheRosterFromTheEndIsAnotherWorld
--- PASS: TestWalkingTheRosterFromTheEndIsAnotherWorld (0.03s)
=== RUN TestOneOrderedPhaseSpendsWhatAnyOtherOrderSpends
--- PASS: TestOneOrderedPhaseSpendsWhatAnyOtherOrderSpends (0.00s)
=== RUN TestAPoolCountsEveryNumberItIsHolding
--- PASS: TestAPoolCountsEveryNumberItIsHolding (0.01s)
PASS
ok theworld/internal/gene 0.095s
The third of those is the one whose body matters. It runs a single phase forward and the same single phase from the end, off two identical plates, and then demands three things: that both spent the same numbers, that both asked the same count of creatures, and that the set of animals in one run's log is exactly the set in the other's. All three hold. The test that would catch the reordering is the first one, and it catches it by folding the world rather than by counting anything.
Hunting first divergence
Nothing above is about creatures, and the procedure is the same in any system where two runs are supposed to agree and do not.
Fold, do not print. Seven hundred thousand ticks of three hundred animals cannot be read. Sixteen characters can be compared by a test, on every commit, for nothing, and a fold that includes every tick answers "is this the same run" without anybody deciding in advance which part of the run to watch.
Keep a coarse tape always and a fine one on demand. A line a year is two hundred lines and costs nothing, and it converts "somewhere in two centuries" into "somewhere in this year". A line a tick is seven hundred thousand lines and answers the question, and it is affordable precisely because the coarse tape has already said how far to run. Two resolutions is the whole method, and it is the same one a bisect over commits uses.
Read the symptom's arithmetic before its content. Five runs, five answers, means something is being randomised on purpose. Two runs that agree with each other and disagree with a third mean two versions of something. Two runs that part on one tick and agree perfectly before it mean a single event, and the thing to do next is print that event and nothing else. The eight rows above are the whole diagnosis, and everything before them was narrowing.
An ordering rule needs a flag, not a comment. Every counterfactual in this volume is switchable, so a claim about what would happen otherwise is a run instead of a memory. That is the difference between "walking the roster backwards would give a different valley" and this page, which can say the tick.
Scaling ticks, memory and bytes
The last question a run of this size raises is what a bigger one would take, and it has three answers rather than one: the ticks, the memory, and the file. They do not scale the same way and only one of them is a fact about a machine.
So measure the scaling instead of guessing it. The same recipe goes on four grounds, the last of them ten times the first, all four founded off the same seed with a herd in proportion and run for four years apiece.
$ go run ./cmd/twice -mode size -years 4
twice: the same recipe on four grounds, 4 years each
every ground is founded off seed 5 and scattered with a herd in
proportion to it on stream 12, bred through the same pass, sorted into
species and archived. The last of the four is ten times the first
16x12: 192 cells, 176 a creature can stand on, 25 founded
74 born, 84 struck off, 15 walking, 123 plants standing at 6572.0 grams
99 genomes held, 16546 numbers in them, 0.13 MB of genome at 8 bytes each
183 records archived, 892439 bytes, 4876.7 bytes a record
13950 ticks in 2.533s, 5508 ticks a second (measured here; yours will differ)
32x12: 384 cells, 368 a creature can stand on, 56 founded
199 born, 206 struck off, 49 walking, 284 plants standing at 16413.5 grams
255 genomes held, 42600 numbers in them, 0.33 MB of genome at 8 bytes each
461 records archived, 2291268 bytes, 4970.2 bytes a record
13950 ticks in 5.047s, 2764 ticks a second (measured here; yours will differ)
32x24: 768 cells, 752 a creature can stand on, 118 founded
215 born, 289 struck off, 44 walking, 643 plants standing at 29804.2 grams
333 genomes held, 55635 numbers in them, 0.42 MB of genome at 8 bytes each
622 records archived, 2993028 bytes, 4811.9 bytes a record
13950 ticks in 10.214s, 1366 ticks a second (measured here; yours will differ)
80x24: 1920 cells, 1904 a creature can stand on, 303 founded
774 born, 891 struck off, 186 walking, 1677 plants standing at 90803.8 grams
1077 genomes held, 180068 numbers in them, 1.37 MB of genome at 8 bytes each
1968 records archived, 9711770 bytes, 4934.8 bytes a record
13950 ticks in 24.618s, 567 ticks a second (measured here; yours will differ)
The standable cells go 176, 368, 752 and 1,904, which is 1, 2.09, 4.27 and 10.82 times the first. Read the other columns against those four numbers. The ticks a second go 5,508, 2,764, 1,366 and 567, so a tick costs 1, 1.99, 4.03 and 9.71 times what it cost on the small ground: the price of a tick tracks the amount of ground within a few per cent the whole way up, because what a tick does is walk everything standing on it. The genomes held go 99, 255, 333 and 1,077 and the archive goes 892,439 bytes to 9,711,770, which is the same column twice over, since a record is written for every birth and every death and nothing else. Those two wander in the middle of the table, because four years is not long enough for four grounds of different sizes to fill at the same rate, and they arrive at the far end at 10.88 times the first against 10.82 times the ground.
Memory here is counted and not measured, which is the only way it can be quoted at all. The pool holds every genome the run has ever made, a genome is a known count of numbers, and eight bytes a number is arithmetic anybody can check on any machine. A reading of the heap would be a fact about this collector on this afternoon, and it would not be the same twice.
Numbers first, and all of them are on this page already. Two centuries of the small valley: 719,550 ticks, 336 animals still walking, 1,750 genomes in the pool holding 294,016 numbers between them, and an archive of 3,164 records in 16,049,646 bytes. The pair of them took seven minutes and fifty-two seconds here.
Now ten times the ground. That is 1,920 cells against 192, of which 1,904 carry a creature against 176, so 10.8 times the standable ground. The room rule has not changed and nothing in it counts animals: a cell of ground carries one body, births are refused when there is nowhere to put them, and the population walks to whatever number that produces. So 336 becomes 336 × 10.8, which is about 3,600 animals.
The three answers are then three multiplications, each with its own measured factor. Ticks: 9.7 times as dear a tick, so seven minutes fifty-two becomes 7.87 × 9.7 = 76 minutes for the pair. Genome memory: 10.9 times as many genomes, so 294,016 numbers becomes about 3.2 million, which at eight bytes each is about 24 megabytes. Archive: 10.9 times as many records, so 3,164 becomes about 34,000, and at the 5,073 bytes a record this run averaged that is about 165 megabytes.
N ≈ 1.9 × A, animals standing on A cells of usable ground
t = T ÷ r, and r falls in proportion to A
bytes = R × c, records times what a record costs
Two things that arithmetic does not cover, and both of them bite on a longer run rather
than a wider one. The archive and the pool grow with every creature that has ever
lived, not with the ones alive, so doubling the length of a run doubles both while the
population sits still. And c is not a constant: a record carries the whole
wiring, and a wiring grows a link or a node every time a birth's four numbers say so.
Over these two centuries the average genome came to 168 genes against a founder's 167,
because a wiring that grows has to survive to be inherited and almost none of them do.
A run whose lineages stayed alive longer would pay more per record every year it ran.
Why run bytes are the result
Everything on this page comes back to one asymmetry. A simulation hands you a number, and the number is worth exactly as much as your ability to produce it again. Not because anybody doubts you: because a number you can produce again is a number you can vary. Change the room rule and re-run and the difference is the rule's doing. Change nothing and re-run and the difference is a bug in your bookkeeping. Without the second half, the first half is guesswork with decimal places on it.
The rules that buy it are small and they are all of the same kind: refuse to let an outcome decide how much work gets done. Draw the same count of numbers whether the birth happens or not. Ask the cheap question before the generator so a creature that cannot breed costs nothing at all. Take a choice with a multiply and a floor, never with a function that redraws when it dislikes the first answer. Add a node's inputs in an order somebody wrote down. Walk the roster the way the roster is ordered. None of them is clever, none costs anything, and every one of them is easy to undo by accident while tidying up, which is what the tape is for.
One clause of that carries further than the rest, and it is the one the failure box paid for: hold the instrument to a higher standard than the thing it measures. A nondeterministic check pointed at a deterministic world reports its own defect in the world's name, and it is convincing while it does it.
- Given a run that will not reproduce, You can name the three kinds of bookkeeping to suspect, and say which of them a count of numbers drawn will never catch.
- You can write a tape for a simulation: what goes into the fold, in what order, why a float goes in as its bits, and why the creatures come off a list and not off a map.
- Shown five runs of one seed giving five different digests, You can say what that rules out, and tell it apart from what a race between goroutines or two versions of a library would produce.
- You can narrow a divergence in a seven-hundred-thousand-tick run down to one tick without recording every tick of it, and say what each of the two tapes costs and buys.
- Reading the eight asks on the tick two runs part on, You can explain why the fourth ask describes a child costing 356.8231 in both of them when the fourth animal is a different animal in each.
- Given one measured run and a ground ten times the size, You can work out the ticks, the genome memory and the archive bytes, and say which of those three is the only one that belongs to a machine.
Exercise 1 — is it the two goroutines that make them agree?
The pair runs side by side, so a suspicious reader might wonder whether the two runs
are agreeing because something is shared between them. Predict what one run alone
folds to, then check with
go run ./cmd/twice -mode twice -years 4 -runs 1 against
go run ./cmd/twice -mode twice -years 4.
One run alone makes 74 animals, leaves 15 walking and folds to
88850053ac0a0767, which is what each of the pair folds to and what the
unchanged row of the seven-run table folds to. Sharing would show up as agreement
that disappears when the runs are separated, and this is the opposite: the
digest is the same whether the valley is ticked beside another one or on its own.
It is a cheap check and it is the right instinct. Two runs in one process can agree
for a bad reason, and the way to find out is to take the process apart. The stronger
version of the same check uses a clean container with the same recorded
toolchain, image content and platform. Compare its first-year digest with
ea2c848aac459b99; a mutable tag alone does not establish those
reference conditions.
Exercise 2 — is the table a fact about seed 5? Six rows agreed
and one moved. Predict whether that pattern is a property of this seed or of the rules,
then run go run ./cmd/twice -mode holds -years 4 -seed 7.
The pattern holds and every number in it changes. Seed 7 folds to
9da8f7dbc6f142b5 across all six rows that must agree, and the pass spends
274,381 numbers instead of 916,356: a valley that asks a third as many creatures for
children over the same four years. The backward row moves as before, to
c67b34d1891e8d5b on 35,630 numbers.
That is the distinction the whole page rests on. The digest is a fact about one seed and is meant to be. Which changes move it is a fact about the rules, and has to be hold across seeds under the declared reference conditions. Testing seed 7 widens the evidence beyond seed 5; neither example proves every possible seed or cross-platform floating-point replay.
Exercise 3 — the portrait, twice. The valley is not the only
thing in this volume with a run behind it. Write the appearance bench's lineage to two
differently named archives with
go run ./cmd/looks -mode draw -limb 200 -roll draw-a.jsonl and again with
-roll draw-b.jsonl, then cmp them.
cmp draw-a.jsonl draw-b.jsonl says nothing, which is
cmp saying the two files are the same 9,576,460 bytes: a thousand and
one births down one branching line, every wiring, every innovation number and every
look gene in the same order. The card hash printed at the end of both runs is
48416baa1f0410a9, and the four tips are the same four animals with the
same coats.
Read the bytes a record while you are here, because they say something the valley cannot. Those thousand and one records average 9,567 bytes against the 5,073 of the two-century valley, and the difference is entirely wiring: a bench lineage breeds every genome it makes, so its controllers grow for a thousand generations, while almost every wiring the valley grows dies before it can be inherited.
So the volume closes with a valley somebody else can run. Twenty-five animals stood up on sixteen by twelve cells of ground, each with a store of two hundred and its ten body factors all at 1.00, and two hundred years later three hundred and thirty-six are walking about on it with no two of them the same numbers: bodies their parents' arithmetic priced, wirings that can grow a connection to a sense every ancestor was deaf to, three genes nothing has ever read, four more that decide a coat. Nothing in those two centuries said what a good animal was. Nothing compared two creatures except to ask whether one was near enough and like enough to be a parent. And the whole of it comes back from a seed and a bench, byte for byte, in eight minutes.
What it cannot do is the thing a tape makes impossible to look away from. One species opened in the first year and one species was standing at the end. The distance, the threshold and the register are built, tested and shown splitting a line on a bench, and the valley never came near them: sixteen cells of ground with a six-cell mate box on it is one neighbourhood, and 1,725 births spread over two centuries put the deepest line fifty generations from a founder where the bench that did split needed two thousand births of one line. So two hundred years of this world, run twice, is one experiment measured twice over, and not two experiments to set against each other.