The World Vol 6 · Evolution
ch 58 / 105
Chapter 58

A Copy With One Number Changed

Fixed-draw mutation

A genome can already be copied: three hundred and ninety-five numbers in the child match three hundred and ninety-five numbers in the parent, and the MIND block gets its own slice. Every gene of a copy takes two numbers off the stream: one to decide whether it moves, and one to decide where it moves to.

The second number is spent whether or not anything uses it. A pass over a genome of 395 numbers is 790 draws every single time, and nothing anywhere in the run can work out how many genes happened to mutate by counting the stream.

Mutation needs that rule because this world compares long runs. If a copy that changes one gene spends fewer draws than a copy that changes ten, the next child in line starts from a different stream position. The difference then leaks into every body, brain and birth after it, so the run has changed more than the gene under discussion.

The operator has two ways to get a gene wrong. A nudge keeps the parent's number and tries next door; most improvements a lineage finds are small. A reset throws the old value away and draws anywhere inside the gene's allowed span, because a lineage also needs a road out of a bad part of the range.

This page keeps both operations behind one roll, then proves the arithmetic. One hundred copied founders carry measurable differences in body factors, controller weights, temperaments and look genes, while the unchanged parts still point back to the same founder row.

Mutation rates from one roll

There are two useful ways for a copy to get a gene wrong, and a mutation operator wants both. The first is a nudge: keep the number the parent had and try just next door. Most of the improvements a lineage ever finds are small ones, because a body that is nearly right is surrounded by bodies that are nearly right, and a small step lands in that neighbourhood on purpose. The second is a fresh start: throw the gene away and write a new one anywhere in the range. A line of copies that only ever nudges can take a very long time to cross a range from one end to the other, and if a lineage has painted itself into a corner, the only way out of the corner in one step is to stop caring where it was.

Both are read off one number. A single draw between 0 and 1 is split into three bands: the bottom sliver replaces the gene, the band above it nudges the gene, and everything above that leaves the gene alone. One number, three answers, no second coin to flip. That matters for the same reason everything on this page matters: a second coin is a third draw, and this pass gets two.

▣ Build · stage 1 — the stream, the rates, and how far a nudge goes
// internal/gene/copy.go
// StreamMutate is the stream every mutation in this world draws from,
// and the one number this file is allowed to spend. It is its own
// stream and not a corner of an existing one, because a stream drawn a
// different number of times is a different world: the founding takes
// its cells off stream 12 and its weights off stream 14, and both of
// them have to read the same numbers in the same order whether anything
// in this valley ever makes a child or not.
const StreamMutate = 16

// Mutations opens the generator every mutation of one run draws from.
func Mutations(seed uint64) *rand.Rand {
	return rand.New(rand.NewPCG(seed, StreamMutate))
}

// Rates is how wrong a copy is allowed to be: the two chances a gene
// moves at all, and how far one step may carry it when it does.
//
// Replace and Perturb are read off one draw as bands of the same
// number, not as two separate coins. A draw under Replace throws the
// gene away and writes a new one anywhere in its range. A draw under
// Perturb and not under Replace nudges the gene it already has.
// Everything else leaves the gene alone. So Perturb is the whole of
// what moves, Replace is the part of that which starts over, and the
// two rates are stated the way they are read.
//
// Step is a share of the gene's own range and not a share of the gene's
// own value, which is the difference between a nudge that means the
// same thing everywhere and a nudge that gets timid near zero. Five
// percent of a body factor's range is 0.1875 either way; five percent
// of a weight's is 0.4000; five percent of a temperament gene's is
// 0.0500.
type Rates struct {
	Replace float64
	Perturb float64
	Step    float64
}

// Usual is what every child in this valley is copied at.
var Usual = Rates{Replace: 0.01, Perturb: 0.09, Step: 0.05}
$ go run ./cmd/copy -mode rates
copy: one pass over a genome of 395 numbers, two draws a gene

  the first draw decides, and it is one number read as three bands

  first draw             the gene         the second draw becomes
  under 0.01             starts over      anywhere in the whole range
  0.01 to under 0.09     is nudged        a step of at most 5 percent of the range
  0.09 and over          is left alone    nothing, and is drawn anyway

  block     genes        low       high        range    one nudge
  BODY         10     0.2500     4.0000       3.7500       0.1875
  MIND        378    -4.0000     4.0000       8.0000       0.4000
  TEMPER        3     0.0000     1.0000       1.0000       0.0500
  LOOK          4     0.0000     1.0000       1.0000       0.0500
    Sight       1     0.5000     2.0000       1.5000       0.0750

  395 genes x 2 draws = 790 numbers off stream 16, every copy, whatever moves
  genes expected to move: 395 x 0.09 = 35.55, of which 395 x 0.01 = 3.95 start over

Look at the one nudge column, because it is doing something the code never says out loud. A nudge is five percent of the gene's range, so the operator hands out a different-sized step to each block without knowing what any block is for. A body factor runs over 3.7500 and gets a step of up to 0.1875. A weight runs over 8.0000 and gets 0.4000. A temperament gene runs over 1.0000 and gets 0.0500. Sight, which carries half the leash the other nine body genes carry because a wider line of sight is arithmetic the phase has to pay for every tick, gets 0.0750: half the step, for free, out of the same line of code. Had the nudge been five percent of the gene's value instead, a gene sitting near zero would barely move at all and a gene sitting near four would move in strides, and a temperament gene that ever reached 0.0000 would be stuck there for ever.

The two numbers one gene of a copy takes off the stream Two boxes across the top. The left one is the first draw, which decides which of three things happens to the gene. The right one is the second draw, which is spent in all three cases whether it is used or not. Arrows lead down from both into a table of three rows. The first row: a first draw under 0.01 makes the gene start over, and the second draw becomes a number anywhere in the whole range. The second row: a first draw from 0.01 to under 0.09 nudges the gene, and the second draw becomes a step within five percent of the range. The third row: a first draw of 0.09 and over leaves the gene alone, and the second draw is taken and thrown away. Beneath the table, the arithmetic: 395 genes times 2 draws is 790 numbers off the stream every copy, and no run can tell from that count what any of them said. ONE GENE OF A COPY, AND THE TWO NUMBERS IT SPENDS THE FIRST DRAW decides which of the three THE SECOND DRAW spent in all three, always under 0.01 starts over anywhere in the whole range 0.01 to under 0.09 is nudged a step within 5% of the range 0.09 and over is left alone taken, and thrown away 395 genes x 2 draws = 790 numbers, every copy and no run can tell from the count what any of them said
Figure 58.1 — the draw pair. The middle column is the biology; the right column is where the second number goes, and on the third row it goes nowhere. Every row spends the same two numbers, so how much of the sequence a copy eats is settled before the first draw is even looked at.

The third row of that figure is the whole trick and it looks like waste. Nine genes in ten come through a pass untouched, and for every one of them the second number is drawn, read by nobody and dropped: about three hundred and sixty numbers a pass, pulled out of the generator and thrown away. What they buy is that the position in the sequence after the pass is a function of the length of the genome and of nothing else at all.

The mutation walk

The operator itself is one loop with a switch in it. It walks the flat run of genes the genome chapter laid down, in block order, BODY then MIND then TEMPER then LOOK, and asks the flat walk for the range of every gene it touches so it never has to know which block it is in.

▣ Build · stage 2 — the pass, and the child it makes
// internal/gene/copy.go, continued
// Moves is what one pass over one genome came to: the numbers it took
// off the stream, and the genes that ended up somewhere else. Draws is
// there to be checked rather than to be read, and the thing to check is
// that it is twice the length of the genome every single time.
type Moves struct {
	Draws     int
	Replaced  int
	Perturbed int
}

// Moved is the genes this pass put somewhere else.
func (m Moves) Moved() int { return m.Replaced + m.Perturbed }

// Mutate walks a genome gene by gene in block order and moves the ones
// the stream says to move.
//
// Two draws a gene, always. The first decides what happens to the gene
// and the second says where it goes, and the second is taken off the
// stream whether or not anything is done with it. That is the whole of
// the discipline, and it is worth being plain about why a pass cannot
// be written the obvious way instead: draw the first number, and only
// reach for a second when the first says the gene is moving. That
// version is shorter, it draws a little over half as many numbers,
// and it makes a run that cannot be replayed, because how many it
// spends on a genome depends on what the numbers said. Every draw after
// the first gene that moves lands at a different place in the sequence.
//
// The clamp is not here. Genome.Put holds every number it is given
// inside that gene's own range, so a perturbation that would walk a
// body factor past four stops at four and a replacement can only ever
// name a number the range already contains.
func (r Rates) Mutate(g *Genome, s *rand.Rand) Moves {
	var m Moves
	for i, n := 0, g.Len(); i < n; i++ {
		what, where := s.Float64(), s.Float64()
		m.Draws += 2
		sp := g.Span(i)
		switch {
		case what < r.Replace:
			g.Put(i, sp.Lo+where*sp.Wide())
			m.Replaced++
		case what < r.Perturb:
			g.Put(i, g.At(i)+(where*2-1)*r.Step*sp.Wide())
			m.Perturbed++
		}
	}
	return m
}

// Child is one parent's genome copied and then copied wrong: a deep
// copy the parent shares nothing with, put through one mutation pass.
// The parent is not touched, which is the whole reason the copy happens
// before the pass and not after it.
func (r Rates) Child(parent *Genome, s *rand.Rand) (*Genome, Moves) {
	g := parent.Copy()
	return g, r.Mutate(g, s)
}

Two things this listing does not do are as load-bearing as the loop. It does not clamp, because Genome.Put holds every number it is handed inside that gene's own range before it writes it, so a nudge that would carry a body factor past four stops at four and there is no road into a genome that skips the check. And Child copies before it mutates, never after, so the parent that made the child is exactly the parent it was before the child existed. Getting that backwards produces a lineage where every ancestor quietly becomes its own descendant, and nothing about it looks wrong until somebody prints a family tree.

▣ Build · stage 3 — how far two genomes have got from each other
// internal/gene/copy.go, continued
// Apart is how far two genomes have got from each other, gene by gene:
// the largest gap anywhere in the flat walk, and which gene it is at.
// It is a plain largest-difference and not a distance with weights in
// it, because at this point in the valley nothing has any business
// deciding that one gene matters more than another.
func Apart(a, b *Genome) (at int, gap float64) {
	at = -1
	for i, n := 0, a.Len(); i < n; i++ {
		d := a.At(i) - b.At(i)
		if d < 0 {
			d = -d
		}
		if d > gap {
			at, gap = i, d
		}
	}
	return at, gap
}
$ go run ./cmd/copy -mode once
copy: one pass over the identity genome, seed 5, stream 16

  genes walked                      395
  numbers taken off the stream      790
  genes replaced outright             3
  genes nudged                       34
  genes left where they were        358

  block     genes    moved    share
  BODY         10        1    10.0%
  MIND        378       35     9.3%
  TEMPER        3        1    33.3%
  LOOK          4        0     0.0%

  every gene outside the MIND block that ended up somewhere else:

       i block    gene                    was         is   moved by
       0 BODY     Bulk                 1.0000     1.0747     0.0747
     390 TEMPER   tameness             0.5000     0.4997    -0.0003

  the gene that moved furthest is 71, weight 61 in MIND, by 3.9256

  the row this child is stamped out of:

     i gene          factor      the row      stamped
     0 Bulk          1.0747      40.0000      42.9896
     1 Full          1.0000     400.0000     400.0000
     2 Basal         1.0000       0.0040       0.0040
     3 Work          1.0000       0.6000       0.6000
     4 Bite          1.0000       0.2500       0.2500
     5 Convert       1.0000       4.0000       4.0000
     6 Reach         1.0000       1.0000       1.0000
     7 Sight         1.0000      12.0000      12.0000
     8 Top           1.0000       0.4500       0.4500
     9 Swing         1.0000       0.0833       0.0833

Thirty-seven genes of three hundred and ninety-five ended up somewhere else, which is nine and a bit percent, and the block shares are 10.0, 9.3, 33.3 and 0.0 percent. The last two are what a rate of nine percent looks like when it is only rolled three or four times: one gene of three is a third, zero genes of four is nothing, and neither says anything about the operator. The one body gene that moved is Bulk, nudged from 1.0000 to 1.0747, which stamps a body of 42.9896 grams instead of 40.0000. One temperament gene moved by three ten-thousandths. Nothing in this valley reads a temperament gene, and it moved anyway, because the operator has no idea which genes anything reads.

The gene that moved furthest is number 71, the sixty-second weight of the controller, by 3.9256. It is a weight for two reasons that will hold on every run of this thing. The MIND block is 378 of the 395 numbers, so nearly every gene is a weight. And a weight's range is 8.0000 wide against a body factor's 3.7500, so a replacement that lands at one end of a weight's range starts further from where it began than any body factor could.

Now push the operator past where it is ever asked to work, so the edges show. Turn the nudge rate up to certainty and the step up to half a range, point it at Sight, and run forty copies in a chain.

$ go run ./cmd/copy -mode clamp -step 0.5 -rungs 40
copy: gene 7, Sight, nudged every copy for 40 copies

  the range is 0.5000 to 2.0000 and one nudge is at most 0.7500

    rung     factor      cells      at an end
       1     0.8882    10.6583             no
       2     1.2057    14.4680             no
       3     1.3124    15.7485             no
       4     1.4959    17.9506             no
       5     1.6618    19.9421             no
       6     1.3930    16.7154             no
       7     0.8977    10.7723             no
       8     1.4060    16.8724             no
       9     0.7768     9.3220             no
      10     0.5000     6.0000 yes, the bottom
      15     0.5000     6.0000 yes, the bottom
      20     2.0000    24.0000   yes, the top
      25     1.2563    15.0753             no
      30     1.6544    19.8524             no
      35     1.6021    19.2249             no
      40     1.2797    15.3559             no

  5 of 40 copies ended sitting on the top of the range and 6 on the bottom
  the factor never left 0.5000 to 2.0000, so the cells never left 6.0000 to 24.0000

Rung 10 asks for a factor below half and gets 0.5000. Rung 20 asks for one above two and gets 2.0000. Eleven of the forty copies finished sitting exactly on an end of the range, and the stamped sight cap never once left the band from six cells to twenty-four. The clamp lives inside the write itself, which is what keeps it from being a validation step some caller can forget. The number that leaves the operator and the number that lands in the genome are allowed to differ, and the one in the genome is always a number this world has prices for. A genome that stored what the operator asked for and clamped on the way out would print a factor above two — the run says one nudge at this step is at most 0.7500, so as high as 2.7500 — in the record of an animal that could only ever see twenty-four cells.

Sitting on an end is a perfectly good place for a gene to be, and the copy that put it there did no less work than any other. A gene pinned at the top of its range is a gene the operator is still pushing at, and one nudge in the other direction moves it off again. What the clamp buys is that no arithmetic anywhere in this world, however many copies deep, can produce a creature with a negative sight cap or a basal rate a hundred times the row's.

The thrifty mutation pass

Here is the operator as almost everybody writes it the first time. Draw the number that decides. If it says the gene is moving, draw a second one to say where. If it does not, move on. It is shorter, it is obviously equivalent, and it spends a little over half as much of the generator.

▣ Build · stage 4 — the thrifty pass, kept so it can be run
// cmd/copy/main.go
// thrifty is the operator as it is tempting to write it: the second
// draw taken only when the first one says a gene is moving. It is here
// to be run and not to be shipped.
func thrifty(r gene.Rates, g *gene.Genome, s *rand.Rand) gene.Moves {
	var m gene.Moves
	for i, n := 0, g.Len(); i < n; i++ {
		what := s.Float64()
		m.Draws++
		sp := g.Span(i)
		switch {
		case what < r.Replace:
			g.Put(i, sp.Lo+s.Float64()*sp.Wide())
			m.Draws++
			m.Replaced++
		case what < r.Perturb:
			g.Put(i, g.At(i)+(s.Float64()*2-1)*r.Step*sp.Wide())
			m.Draws++
			m.Perturbed++
		}
	}
	return m
}
$ go run ./cmd/copy -mode count
copy: what a pass costs the stream, at four settings of the same operator

  rates                             draws    moved 2 x genes
  replace 0.00, perturb 0.00          790        0      790
  replace 0.01, perturb 0.09          790       37      790
  replace 0.00, perturb 1.00          790      395      790
  replace 1.00, perturb 1.00          790      395      790

  three children in a row off one stream, made two ways

  child        fixed    thrifty   and the two of them
  1              790        441   differ most at gene 71, by 3.9256
  2              790        421   differ most at gene 71, by 4.0381
  3              790        441   differ most at gene 71, by 4.0381
  total         2370       1303

  the fixed rule spent 2370 numbers, the thrifty one 1303
  every child after the first is a different child, on the same seed

The top table is the promise. Rates that move nothing at all, the rates this valley actually uses, rates that nudge every gene, and rates that replace every gene: four operators with nothing in common except the length of the genome, and every one of them takes 790 numbers. A run that ships a change to the mutation rates does not have to think about what that does to the stream, because it does nothing to the stream.

The bottom table is what the thrift costs. Three children in a row, made from one seed by each operator. The fixed rule spends 790, 790 and 790. The thrifty one spends 441, then 421, then 441: three different numbers, and the reason they differ is that a different number of genes happened to move each time. By the third child the two operators have spent 2,370 numbers and 1,303 numbers of the same sequence, so they are reading in two different places and will be for the rest of the run.

The line to sit with is the first one. Child 1 already differs, most at gene 71, by 3.9256. Both operators read the same first number for gene 0 and agreed about what to do with it. They part company at the first gene neither of them moved: the fixed rule burned its second number there and the thrifty one did not, so from the next gene on the two passes were reading the sequence one step out of step. Nothing about that shows up as an error. Both children are valid genomes. Both runs are perfectly deterministic on their own. They are just two different worlds, and if the one you are trying to reproduce is the other one, no amount of holding the seed fixed will get you back.

This is the rule that makes replay possible at all, and it generalises past mutation without changing a word: the number of draws an operation takes must be a function of its inputs' sizes and never of its inputs' values. A pass over a genome of G numbers costs 2G draws. Not "about 2G". Not "2G on average". Exactly 2G, on a genome of all zeroes, on a genome that has been copied ten thousand times, at any rates anyone ever sets.

One hundred founder copies

One pass moves a genome hardly at all. Ninety-one percent of it is untouched, and of the nine percent that moved, eight parts in nine only shuffled a little. The interesting question is what a hundred of those in a row come to, because that is what a lineage is: a chain of copies, each one made from the copy before it, with nothing anywhere keeping a record of the original.

$ go run ./cmd/copy -mode ladder
copy: 100 copies in a chain from the identity genome, seed 5

    rung    draws    moved       Bulk      Sight        Top   furthest
       1      790       37     1.0747     1.0000     1.0000     3.9256
      10     7900      367     1.0747     1.0107     1.0000     4.0000
      25    19750      888     1.0787     0.9832     0.9019     3.9641
      50    39500     1770     0.9782     1.7826     0.8043     4.0000
      75    59250     2675     1.0770     1.7826     0.8829     4.0000
     100    79000     3574     1.1402     1.9591     0.6117     4.0000

  79000 numbers off stream 16 for 100 copies, and 3574 genes moved

  the gene that wandered furthest in each block:

    block         i gene                started      ended   moved by
    BODY          1 Full                 1.0000     2.4010     1.4010
    MIND         10 weight 0             0.0000     4.0000     4.0000
    TEMPER      390 tameness             0.5000     0.2512     0.2488
    LOOK        391 coat hue             0.5000     0.0561     0.4439

  furthest of all 395: gene 10, weight 0 in MIND, 4.0000 from where it started

Follow the three body columns down. Bulk starts at 1.0000, is nudged on the first rung to 1.0747, and is still at 1.0747 at rung 10, because nine more passes went by without the first draw for gene 0 ever coming in under 0.09. By rung 100 it is 1.1402, having been touched eight or ten times in a hundred passes and having wandered up and back. Sight goes the other way: 1.0000 on the first rung, barely off it by rung 10, and 1.9591 at the end, with most of that gained in one jump between rung 25 and rung 50 where a draw under 0.01 landed on it and it started over near the top of its range. Top drifts down from 1.0000 to 0.6117. Three genes, three completely different histories, off one operator with one setting.

The per-block table at the bottom is the one to keep. In BODY the furthest traveller is Full, which more than doubled. In TEMPER it is tameness, which nothing reads and which moved anyway. In LOOK it is the coat hue, which fell from the middle of its range almost to the floor. And in MIND, weight 0 went from 0.0000 to exactly 4.0000 and stopped there, which is the clamp again: the operator went on pushing and the range refused.

The same hundred rungs read out as records instead of as a table, six of them, one line of JSON each. It is the same ladder either way, and a line-per-record file is the form something other than a person can read without a parser of its own.

$ go run ./cmd/copy -mode tape -rungs 6
{"rung":1,"draws":790,"moved":37,"bulk":1.0747,"sight":1,"top":1,"gene":71,"gap":3.9256}
{"rung":2,"draws":1580,"moved":70,"bulk":1.0747,"sight":1,"top":1,"gene":71,"gap":3.9256}
{"rung":3,"draws":2370,"moved":104,"bulk":1.0747,"sight":1.0107,"top":1,"gene":71,"gap":3.9256}
{"rung":4,"draws":3160,"moved":145,"bulk":1.0747,"sight":1.0107,"top":1,"gene":71,"gap":4}
{"rung":5,"draws":3950,"moved":196,"bulk":1.0747,"sight":1.0107,"top":1,"gene":71,"gap":4}
{"rung":6,"draws":4740,"moved":237,"bulk":1.0747,"sight":1.0107,"top":1,"gene":71,"gap":4}

gap holds at 3.9256 for three rungs and then goes to exactly 4, which is a weight arriving at the top of its range and being held there. draws climbs by 790 a line and by nothing else, which is the whole of this chapter's discipline written down as a column of numbers you can check by subtraction.

∑ Math Interlude — how far a hundred small steps carry a gene

Start with how many genes move. Every gene is looked at once a pass, and it moves when the first draw comes in under 0.09. Over 395 genes that is

moved = G × p = 395 × 0.09 = 35.55

and the run above printed 37 on its first pass and 3,574 over a hundred passes, which is 35.74 a pass. Those are the same number with the roughness a real draw has.

Now one gene, over many copies. Being nudged is rare, so ask how long the wait is. A gene is nudged when the draw lands between 0.01 and 0.09, a band 0.08 wide, so on average it waits

1 ÷ 0.08 = 12.5 copies

between one nudge and the next, and it starts over completely once in a hundred copies. In a hundred rungs a given gene is nudged about eight times and replaced about once, and that is why Bulk above sat still for whole stretches of the ladder.

Each nudge is a step of a size drawn evenly between minus and plus the nudge size, and the steps are independent, so they do not add up: they partly cancel. A run of n steps of typical size s ends up about

spread = s × √n

away from where it started, not s × n. That square root is why a hundred copies is not a hundred times as far from the parent as one copy. For a body factor the nudge is at most 0.1875 and its typical size is about 0.108, so eight nudges carry a gene about 0.108 × √8 = 0.31 away from where it began. Bulk in the run above ended 0.1402 from 1.0000, which is inside that, and Top ended 0.3883 away, which is right on it.

The replacement rate is the other half of the story, and it does not care about square roots at all. One copy in a hundred throws a gene anywhere in its range, so a hundred rungs of ladder gives each gene about one chance to cross its whole range in a single step. Sight took that chance: it stood at 0.9832 on rung 25 and at 1.7826 on rung 50, and nudging cannot plausibly account for that: a nudge moves a sight factor by at most 0.0750, a gene is nudged about one copy in twelve, and two or three nudges do not cover eight tenths of a factor. Nudging is how a lineage searches near where it is; replacing is how it gets anywhere else.

Ggenes in a genome; 395 here
pthe chance one gene moves in one copy; 0.09 here
nhow many times one gene has actually been nudged
sthe typical size of one nudge, in the gene's own units
spreadhow far from its starting value a nudged gene has has got
√nthe square root of n: the number that multiplied by itself gives n
a × ba multiplied by b
a ÷ ba divided by b

The ground a copied body walked through

A ladder in a text file is a claim about numbers. Put the same copies into The Hollow and they become a claim about animals. The bench founds the herd exactly as it has always been founded, taking its cells off stream 12 and a brain apiece off stream 14, and then puts every founder's genome through twenty-four mutation passes off stream 16 before the row is stamped. Nineteen creatures are founded, which is what that founding has always produced, because nothing this chapter does touches a draw either of those two streams was going to make.

Then the ledger stopped closing.

⚠ Worked failure — the grams go missing one carcass at a time
$ go run ./cmd/copy -mode valley -adrift
copy: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams

  19 creatures founded on stream 12, a brain apiece off stream 14,
  and every genome put through 24 mutation passes off stream 16
  the ground rules are the old ones: one cell of step check, and no rim

  the five fastest of them, in cells a tick at a sprint:

        id        Top    the row coast, cells
         5     1.7613     0.4500       2.6420
        16     1.4938     0.4500       2.2407
         8     1.1887     0.4500       1.7831
        12     0.4977     0.4500       0.7465
         3     0.4854     0.4500       0.7281

                                               this run
  ticks run                                        6750
  creatures struck off                               19
  creatures still walking                             0
  creature-ticks spent past the last cell              1
  grams of carcass buried off the grid        40.000000
  grams in                                 44687.958166
  grams out                                44647.958166

  the two ledgers differ by 40.000000 grams, which is not a rounding

Forty grams, exactly. A ledger that closes leaves a residue of a fraction of a nanogram, the last bits of tens of thousands of additions. This is a whole number of grams that went into the valley and cannot be found anywhere in it, and forty is the founding row's own body mass, so it is one entire body gone at once and not a leak spread over thousands of transfers.

The line above it says where. One creature-tick was spent past the last cell of the grid, and 40.000000 grams of carcass were buried off the grid. The ground the valley keeps books on is a rectangle of cells; the litter bed takes a pile and drops it without a word when the cell it is handed is not one of its own. So a body that ran off the edge of the world and starved out there was written into the death list, counted as struck off, counted as grams that had left the living, and then handed to a bed that quietly refused it. Both halves of the roster's books said the carcass had gone somewhere. Nowhere was somewhere.

The cause is four lines further up the same run, in the coast column. Creature 5 sprints at 1.7613 cells a tick where the founding row sprints at 0.4500, which is a Top factor of 3.9140 on the row's own number. Creature 16 does 1.4938 and creature 8 does 1.1887. The step check that decides whether an animal may put weight somewhere asked one question and had asked it for as long as there had been animals: is the cell one cell along the heading ground You can walk on. That question is right if and only if the body cannot get past that cell, and a body cannot get past that cell only if the legs can stop it inside one. Nothing in this world stops instantly. A tick of legs can change a velocity by a quarter of the top speed, so a body running flat out takes four ticks to stop and travels one and a half times its top speed doing it. At 0.4500 that is 0.6750 of a cell, safely under one, and the discrete question was the right question by luck of the arithmetic. At 1.7613 it is 2.6420 cells, and the check is being asked about the first third of a step whose last two thirds nobody is checking at all.

That hole has been in the ground rules since the first creature stood up, and nothing could reach it while every animal in the world had the same legs. A genome is the first thing in this valley that can.

Two things have to change, and both of them are corrections to what internal/beast already does rather than new machinery bolted onto it. The first is the check: it has to cover the step, and the step is however far the legs have committed the body, never fewer than the one cell it always looked at.

▣ Build · stage 5 — how far a body is committed, and checking all of it
// internal/beast/move.go
// Coast is how far this body still travels after the legs stop pushing,
// in cells. A tick of legs may change a velocity by no more than one
// Ramp's worth of the top speed, so a body asked to stop sheds that
// much a tick and covers whatever speed is left over each time, until
// there is none left to shed. It is worked out at the fastest this
// creature may be asked to go this tick, because that is the speed a
// step it is allowed to take can leave it carrying.
func (b *Beast) Coast() float64 {
	d := b.Kind.Top / Ramp
	if d <= 0 {
		return 0
	}
	far := 0.0
	for v := b.Pace() - d; v > 0; v -= d {
		far += v
	}
	return far
}
// internal/beast/act.go
// ground refuses a step the valley will not carry. It tests the ground
// the step covers and not the cell underfoot, because the cell
// underfoot has already been walked on.
func ground(b *Beast, v *View) error {
	if _, no := b.Blocked(v); no {
		return ErrNoGround
	}
	return nil
}

// Along is the cell d cells along the heading from where the body is
// standing.
func (b *Beast) Along(d float64) sim.Coord {
	p := b.Pos.Scale(1.0 / terra.Tile)
	return sim.Coord{
		X: int(math.Floor(p.X + math.Cos(b.Face)*d)),
		Y: int(math.Floor(p.Y + math.Sin(b.Face)*d)),
	}
}

// Ahead is the first cell of a step: one cell along the heading from
// where the body is standing.
func (b *Beast) Ahead() sim.Coord { return b.Along(1) }

// Blocked is the first cell of this creature's step that the valley
// will not carry, and whether there is one.
//
// A step is not one cell. A tick of legs cannot stop a body dead, so
// the ground a step commits a creature to is everything the body is
// still crossing when the legs have finished with it: b.Coast(), and
// never less than the single cell this rule looked at for as long as
// every animal had the same legs. A body that can stop inside a cell is
// a body the old arithmetic was right about, and every creature stamped
// out of the founding row coasts 0.6750 of a cell.
//
// The walk goes out a cell at a time and stops at the first cell it
// does not like, so a refusal names the cell the creature would have
// run into rather than the far end of a step it never gets to take.
// Nothing here allocates: the ranking walk asks five or six times a
// tick, and the loop is over a count of cells and not a slice of them.
func (b *Beast) Blocked(v *View) (sim.Coord, bool) {
	far := 1.0
	if !b.Adrift {
		if coast := b.Coast(); coast > far {
			far = coast
		}
	}
	for d := 1.0; ; d++ {
		if d > far {
			d = far
		}
		c := b.Along(d)
		if !v.Valley.Bed.In(c) || v.Valley.Bed.Kind(c) == sim.Water {
			return c, true
		}
		if d >= far {
			return c, false
		}
	}
}

Along is the old Ahead with a distance on it, and Ahead is now that function asked for one cell, which is the same arithmetic the old line did: multiplying by exactly 1.0 hands a float64 straight back. So for any creature whose coast is under a cell, Blocked looks at b.Along(1), finds it is at or past the end of the walk, and returns. One cell, one comparison, the same answer the old rule gave, for every cell of the valley in every heading. Every creature stamped out of the founding row coasts 0.6750 of a cell, so the repaired check and the old one are the same check for every animal this book has printed a run about, and that is a fact about the arithmetic rather than a hope about the seeds. The refusal itself got sharper for free: Blocked hands back the cell it stopped on, so the *Refusal the log carries names the cell the creature would have run into and not the first cell of a step it never got to take. errors.Is(err, beast.ErrNoGround) is the same question it always was.

The second change is the rim. A check keeps a creature from choosing to run at ground that is not there; it cannot promise that momentum never carries one over anyway, because a body that is already moving has already spent that decision. The valley is one plate of ground with rock at its edge, and a body over the rock is still a body: the ground under it is the last ground the valley has, and that is the cell a carcass falls on.

▣ Build · stage 6 — a body is always over some cell of the valley
// internal/beast/act.go, continued
// Underfoot is the cell of the valley this body is over: the cell it is
// standing on, or, for a body that has been carried past the last cell
// there is, the rim cell it is lying against.
//
// A position is allowed off the grid, because a body carries momentum
// no tick of legs can cancel and the rim is one cell wide. A body is
// not allowed to be nowhere. Everything this world writes onto the
// ground under a creature comes through here, and the ground under a
// creature is always ground the valley has.
func (b *Beast) Underfoot(bed *terra.Bed) sim.Coord {
	c := b.Cell()
	if b.Adrift {
		return c
	}
	switch {
	case c.X < 0:
		c.X = 0
	case c.X >= bed.W:
		c.X = bed.W - 1
	}
	switch {
	case c.Y < 0:
		c.Y = 0
	case c.Y >= bed.H:
		c.Y = bed.H - 1
	}
	return c
}
// internal/beast/roster.go — inside Roster.Phase
		if t.Left <= 0 {
			b.Dead = true
			c := Carcass{Who: b.ID, Name: b.Name, At: b.Underfoot(v.Bed),
				Grams: b.Kind.Bulk, Tick: v.Now}
			died = append(died, c)
			r.gone = append(r.gone, home)

One expression changed in the phase: the carcass is written at b.Underfoot(v.Bed) instead of at b.Cell(). For every body standing on the grid those are the same cell and the same bits, so this cannot move a run in which nothing ever left the valley. It can only ever act on a body the step rule had already failed to hold, which is the narrowest thing a repair can be and still be a repair.

▣ Build · stage 7 — the old rules behind a flag, and the valley under the new ones
// internal/beast/body.go — inside type Beast struct
	// Adrift is the ground rules as they were written while every
	// animal in the world had the same legs: the step check looking one
	// cell along the heading and no further, and a body allowed to be
	// nowhere at all. It is kept behind a flag so what those two cost a
	// valley of unequal creatures can be run instead of described, and
	// it is false in every creature this book ships.
	Adrift bool
$ go run ./cmd/copy -mode valley
copy: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams

  19 creatures founded on stream 12, a brain apiece off stream 14,
  and every genome put through 24 mutation passes off stream 16

  the five fastest of them, in cells a tick at a sprint:

        id        Top    the row coast, cells
         5     1.7613     0.4500       2.6420
        16     1.4938     0.4500       2.2407
         8     1.1887     0.4500       1.7831
        12     0.4977     0.4500       0.7465
         3     0.4854     0.4500       0.7281

                                               this run
  ticks run                                        6750
  creatures struck off                               19
  creatures still walking                             0
  creature-ticks spent past the last cell              0
  grams of carcass buried off the grid         0.000000
  grams in                                 44970.601626
  grams out                                44970.601626

  the two ledgers differ by -8.076e-10, which is the last bits of the adding

Same seed, same nineteen founders, same twenty-four passes, same three fast animals with the same coasts. No creature-ticks past the last cell now instead of one, no grams buried off the grid instead of forty, and the two ledgers differ by -8.076e-10, which is what tens of thousands of additions taken in two orders cost. The grams total moved, from 44687.958166 to 44970.601626, and it moved because three creatures out of nineteen are now refused steps they used to be allowed, so they graze different plants and the valley grows a slightly different amount of tissue. That is the repair doing its job. A run whose books do not close has no claim on any of its other numbers either, so there was nothing there to preserve.

Two properties of that repair are the kind that break quietly, so they are tests. The first walks a founding-row creature over every cell of the valley in eight headings, asks both the old check and the new one, and fails if a single refusal names a different cell. The second puts a body past the rim on purpose and asks where it would be buried.

$ go test -count=1 -run 'AStepIsChecked|NoBodyIsBuried' -v ./internal/beast/
=== RUN   TestAStepIsCheckedToTheEndOfIt
--- PASS: TestAStepIsCheckedToTheEndOfIt (0.00s)
=== RUN   TestNoBodyIsBuriedOffTheValley
--- PASS: TestNoBodyIsBuriedOffTheValley (0.00s)
PASS
ok  	theworld/internal/beast	0.002s

The first of those has a guard at the top of it that matters more than the loop does: it checks that the founding row's coast really is under a cell before it compares anything. Without that line the test would go on passing if somebody made creatures faster by default, while quietly comparing the new check against itself and proving nothing.

Why fixed draws preserve comparison

The mutation operator and the ground repair are the same idea twice, which is why they share a page. Both are about a quantity that was constant for so long that something started depending on it being constant.

For the stream it is the draw count. Nothing in this world ever asked how many numbers a copy spends, because until this page nothing copied anything. The moment something does, the count becomes part of the interface between the operator and every other user of that sequence, and an interface nobody wrote down is an interface that will be broken. Fixing the count at 2G is what turns "the mutation operator" into a component with a stated cost: it is allowed to change its rates, its bands, its step size and its whole internal design without any of that being visible to anything downstream of it. The generator was always deterministic on its own; what reproducibility actually rests on is the agreement about how much of it each thing eats.

For the valley it is the top speed. The step check treated a move as a hop from one cell to the next, which is exactly true when a body cannot travel or coast further than one cell in the time it takes the legs to stop it. Every creature in every run this book had printed satisfied that, so the check and the world agreed, and nothing on any page distinguished "this rule is right" from "this rule happens to be right for these animals". A genome makes animals nobody wrote the rules for, and the first thing this one did with that freedom was find the rule that had been resting on a number instead of on an argument.

The general form is plain, because it is not a fact about creatures. When a discrete check stands in for a continuous quantity, the check is only as good as the bound that made the discretisation safe, and that bound has to be written down next to the check or it will be lost. Blocked now derives its own bound: it asks the body how far it is committed and walks that far. It cannot go stale when the numbers change, because it reads the numbers.

✓ Checkpoint — mutation, the draw count, and the rim
  • Given a genome of any length, You can say how many numbers one mutation pass takes off stream 16, and say why that count does not change when the rates do.
  • You can read a single draw as the three bands of the operator, say which band replaces and which nudges, and give the size of one nudge for a body gene, a weight and a temperament gene.
  • You can explain why a nudge is a share of the gene's range and not of the gene's value, and name the gene that gets a smaller nudge for free because of it.
  • Handed the number of times a gene has been nudged, You can estimate how far it has wandered, and say why a hundred copies is not a hundred nudges' worth of distance.
  • You can say what a body's coast is, work it out from a top speed, and explain why a check on the next cell alone was correct at 0.4500 and wrong at 1.7613.
  • Shown a run whose grams ledger is out by a whole body mass, You can name the two places a carcass can be lost and say which one leaves the death counted and the litter unchanged.
⚡ Exercises — try first, then reveal
Exercise 1 — a copy that changes nothing. Predict the draws and the genes moved for go run ./cmd/copy -mode count's first row, where both rates are zero, then say what the stream position is afterwards and why anybody needs to care.

No genes move and 790 numbers are drawn, which the run prints. The first draw of every pair is compared against 0.00 and fails both bands; the second is drawn anyway and dropped. Afterwards the generator sits exactly 790 numbers along, which is where it would sit if every gene in the genome had been replaced.

Why care: it means a run can carry a creature that does not mutate at all beside one that mutates hard, in the same valley, off the same stream, without the still one shifting the sequence the moving one reads. A rate is a setting on a creature and not a setting on the world.

Exercise 2 — how many copies to double a body. Work out roughly how many copies it takes for the Bulk factor to reach 2.00 from 1.00 by nudging alone, then check the ladder run for whether it did anything like that.

A nudge on a body factor is drawn evenly over plus or minus 0.1875, so its typical size is about 0.108. Wandering 1.00 away needs a spread of 1.00, and spread is s × √n, so √n = 1.00 ÷ 0.108 = 9.26 and n is about 86 nudges. A gene is nudged about once in 12.5 copies, so that is roughly 1,070 copies, and because the walk is as likely to go down as up it is a typical distance and not a deadline.

The ladder run bears that out: after a hundred copies Bulk is at 1.1402. Full, the furthest-travelled body gene on that run, reached 2.4010, and it did not walk there. It was replaced, which is the one move that crosses a range without waiting for the square root.

Exercise 3 — a faster valley, both ways. Run go run ./cmd/copy -mode valley -passes 40 -adrift and then the same command without -adrift. Say what the repaired rules changed and what they did not.

Forty passes make faster animals than twenty-four: the fastest three coast 2.6420, 2.1699 and 1.8050 cells. Under the old rules that valley spends 281 creature-ticks past the last cell of the grid and buries 166.002575 grams of carcass off it, and the ledgers part company by exactly that figure. Under the repaired rules the same valley spends 123 creature-ticks outside and buries none of them outside, and the books close to -2.983e-10.

Read the two numbers that did not go to zero. Creatures still leave the valley, 123 times, because the step check governs what an animal may choose and not what its momentum does with a choice already made. What the rim guarantees is narrower and enough: wherever a body ends up, the ground it is buried on is ground the valley has. A repair that made the first number nought as well would have to stop bodies at the rock, and stopping a body is a change to every run in the book rather than to the runs that were already broken.

Every animal in that valley was copied from one founder, and nothing in the valley decided which of the copies deserved to make the next one: all nineteen starved before the two years were out. Twenty-four passes of drift produced a creature that sprints nearly four times as fast as the row it came from, and the ground it ran over had no opinion about whether that was an improvement. Before a lineage can be said to be getting better at anything, there has to be somewhere to measure it, and it cannot be a valley where the weather, the plants and the neighbours are all moving while you watch.