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

A Bench That Keeps Score

Fitness needs an arena

Nineteen animals founded in The Hollow were mutated copies of one founder, and every one of them starved before the second year was out. Some ran faster, some turned harder, and some burned through their stores before they found food. The arena draws its ground, its food and its trials once, then draws nothing while it scores.

A genome's number is a fact about that genome. It does not depend on which genomes were scored before it, how many goroutines the work went out on, or what time of the world's own weather cycle happened to arrive.

The open valley is the wrong measuring tool. Weather changes the ground under the same animal. Plants grow back between attempts. A birth or a death changes the roster and the next creature's surroundings. Those are world facts, but they make a bad ruler.

The arena removes those moving parts and keeps the one question this volume needs: how much standing tissue does this genome's body and controller collect in six hundred ticks when every genome gets the same start. A score that cannot answer that question cannot select parents without selecting noise.

The chapter builds a fixed plate, runs sixty-four genomes through it in population order, and prints one number per genome. The later selection code can read the cards without knowing how the arena made them.

The arena ground

The ground first, because everything else is laid out on it. The arena is twenty-four cells on a side. The outer ring of it is open water and the four hundred and eighty-four cells inside that are soil.

The ring is doing real work and it is not scenery. The step check the last page repaired asks whether the ground a creature's step commits it to is ground the valley will carry, and it counts open water as ground it will not. A creature that tries to run at the ring is refused the action and spends its tick standing there. So the plate is closed by the same rule that stops an animal walking into the pond, and no wall had to be invented for it.

▣ Build · stage 1 — the ground, and the size of the bench
// internal/gene/arena.go
// The two streams the arena spends, and the only random numbers
// anything in this file ever asks for.
const (
	// StreamDrawn is the arena's opening genomes: one generation of
	// them, genome by genome in population order, a fixed count of
	// draws each.
	StreamDrawn = 15

	// StreamPlate is the arena's trial layouts, drawn once when the
	// arena is built and never drawn again. Scoring a genome takes no
	// numbers off it and no numbers off anything else, which is what
	// makes a score a fact about a genome instead of a fact about when
	// it was scored.
	StreamPlate = 21
)

// The size of the bench, written down in one place.
//
// Plate is the cells on a side of the ground, Crowd is the genomes in
// one generation, Trials is the runs each of them gets, and Ticks is
// how long one of those runs lasts. Larder is the stands of fodder a
// trial is laid out with and Stock is the grams standing in each of
// them, so the food in a trial is fixed before the first creature
// arrives and no creature can eat enough of it to matter to the next
// one.
const (
	Plate  = 24
	Crowd  = 64
	Trials = 4
	Ticks  = 600
	Larder = 24
	Stock  = 20.0
	Fodder = 2 // the row of terra.Flora the larder is laid out in
)

// NewPlate is the arena's ground: a square of soil with a ring of open
// water round it. The ring is the whole of what makes the plate closed.
// A creature may not choose a step whose ground it cannot stop on, and
// open water is ground no step is allowed to cover, so the ring is a
// rule the creature runs into rather than a wall somebody built.
func NewPlate(n int) *sim.Grid {
	g := sim.NewGrid(n, n)
	for y := 0; y < n; y++ {
		for x := 0; x < n; x++ {
			c := sim.Coord{X: x, Y: y}
			if x == 0 || y == 0 || x == n-1 || y == n-1 {
				g.Set(c, sim.Water)
			} else {
				g.Set(c, sim.Soil)
			}
		}
	}
	return g
}

Six hundred ticks is a minute of world time at ten ticks a second, and a creature stamped out of the founding row could cross the plate a dozen times in that minute at a full sprint. Four trials and not one, because a single trial is a single starting cell and a single heading, and a genome that happens to open facing a stand of scrub would beat a better one that opened facing the water. Four is a small number, and it is enough to make the luck of one placement cost a quarter of the score instead of all of it.

Now the trials themselves. A trial is where the fodder stands, where the creature is stood up, and which way it is facing when the first tick opens. All three are drawn off stream 21, once, when the arena is built, and then they stop being random numbers and start being data. That is the difference the whole page turns on: a layout that is redrawn per genome is a different exam for every candidate, and a layout that is written down is the same exam for all of them.

▣ Build · stage 2 — four trials, drawn once and then written down
// internal/gene/arena.go, continued
// Layout is one trial written down: the cells the fodder stands on, the
// cell the creature is stood up on and the heading it opens facing. It
// is drawn once and then it is data, so re-founding a trial is reading
// this struct back and never asking the generator anything.
type Layout struct {
	Fodder []sim.Coord
	At     sim.Coord
	Face   float64
}

// Arena is the bench a genome is scored on: one plate of ground and the
// trials every genome runs, in the order every genome runs them.
//
// Crew is how many goroutines the generation is scored across, on the
// same interleaved stripes the creature phase casts its fans on. It
// changes how long a generation takes and it cannot change what a
// generation comes to: scoring a genome reads the layouts and writes
// one card, touches nothing any other genome touches, and draws no
// random number at all.
type Arena struct {
	Ground *sim.Grid
	Trials []Layout
	Crew   int
}

// NewArena builds the bench and draws every trial it will ever run.
//
// The draws are all here, and there are exactly Larder plus two of them
// a trial: one per stand of fodder, one for where the creature is stood
// up and one for the heading it opens facing. A draw that lands on a
// cell something is already standing on is spent and plants nothing,
// the way the valley's own founding spends one, so the count does not
// depend on where the cells fell.
func NewArena(seed uint64, trials int) *Arena {
	g := NewPlate(Plate)
	a := &Arena{Ground: g, Trials: make([]Layout, trials)}
	// ... the soil cells and the walkable cells, in row order ...
	s := rand.New(rand.NewPCG(seed, StreamPlate))
	for i := range a.Trials {
		var l Layout
		taken := map[sim.Coord]bool{}
		for j := 0; j < Larder; j++ {
			c := soil[s.IntN(len(soil))]
			if taken[c] {
				continue
			}
			taken[c] = true
			l.Fodder = append(l.Fodder, c)
		}
		l.At = ground[s.IntN(len(ground))]
		l.Face = s.Float64() * 2 * math.Pi
		a.Trials[i] = l
	}
	return a
}
$ go run ./cmd/score -mode plate
score: the arena, 24x24 cells, 64 genomes, 4 trials of 600 ticks

  the ground                                    cells
  the plate                                       576
  soil a stand of fodder can root in              484
  open water round the rim                         92

  what is switched off                        setting
  rain offered on an average day               0.0000
  what the sun lifts off a wet cell            0.0000
  the share of a litter pile that rots         0.0000
  daylight landing on an open cell             0.0000
  seeds waiting in the bank                         0
  the valley's own tick is never called: nothing here grows, seeds or rots

   trial   stands        grams  stood up on     facing
       1       23     460.0000        20,13     4.6396
       2       24     480.0000         19,7     4.5498
       3       23     460.0000         16,1     4.5176
       4       24     480.0000          7,9     6.0547

  1880.0000 grams of fodder a genome, and the same 1880.0000 grams for every one of the 64
  26 draws a trial off stream 21, 104 for the whole arena, and none ever again
  395 draws a genome off stream 15, 25280 for the generation
  and not one number off any stream while a genome is being scored

Trials one and three hold twenty-three stands and trials two and four hold twenty-four, out of twenty-four draws apiece. The missing one in each is a draw that landed on a cell another stand was already on: the draw is spent and nothing is planted, exactly as the valley's founding generation does it, so a trial costs twenty-six numbers whatever those numbers say. All four layouts together cost a hundred and four, and the arena never touches stream 21 again for as long as it runs.

The block of zeroes in the middle of that listing is the whole of what makes this a bench. Rain is nothing, so no cell gets wetter. Evaporation is nothing, so no cell dries out. Decay is nothing, so a pile of litter stays a pile of litter. There is no daylight at all, and no seed is waiting in the ground. Underneath all five of those there is a stronger statement, and it is the last line of the block: the valley's own tick is never called. The arena runs the creature phase and nothing else. So the fodder does not grow while it is being eaten, the fodder does not seed, no plant dies of bad luck, and the water in the ground sits at exactly full in every cell of the plate for all six hundred ticks. Every reading a creature takes of the moisture slope is a flat zero, because the moisture is flat.

The larder is the one thing on the plate that can change, and it changes in exactly one direction. Four hundred and sixty or four hundred and eighty grams of scrub go into a trial, laid out twenty grams to a stand, and the only way a gram leaves is into a mouth. Add the four trials up and every genome in the generation is offered the same 1,880.0000 grams. That number is the reason two scores can be compared at all.

One genome stepped six hundred times

A trial is one genome stamped into a body, wired to its own controller, and stepped six hundred times. Nothing about that is new: stamping a row out of a genome is the previous two pages, and the creature phase has been building a view, casting every fan, stepping the roster in order and burying the dead since the valley learned to hold animals. What the arena adds is the counting.

▣ Build · stage 3 — one genome, one layout, six hundred ticks
// internal/gene/arena.go, continued
// Trial is one genome on one layout: the ground stood back up, one
// creature stamped and wired, and Ticks creature phases run against it.
//
// The valley's own tick is never called. Rain, sun and decay are set to
// nothing, the seed bank is left empty and no light is cast, so nothing
// in here grows, rots, seeds or dies of the weather. The larder is the
// grams it was laid out with less the grams this creature has taken out
// of it, and that is the only thing on the plate besides the creature
// that ever changes.
func (a *Arena) Trial(g *Genome, l Layout) Card {
	v := terra.NewValley(a.Ground, 0,
		terra.Cell{Moisture: 1.25, Nutrient: 12.0}, 0,
		terra.NewAir(a.air, field.Drag{B: 0.25}, 0, 0))
	v.Fall, v.Sun, v.Decay = 0, 0, 0
	v.Wear, v.Brake, v.Soak = 0, 0, 0
	for _, c := range l.Fodder {
		st := v.Plant(Fodder, c)
		v.Opened += Stock - st.Plant.Mass
		st.Plant.Mass = Stock
	}

	b := g.Stamp(beast.Fauna[0]).Spawn(beast.Centre(l.At), 0)
	b.Face = l.Face
	w := beast.NewWits(g.Net(), b)
	r := beast.NewRoster()
	r.Add(b)
	r.Seen = func(view *beast.View) { beast.Watch(r.Live, view) }
	sc := beast.Scale{Wet: v.Full, Food: beast.Fauna[0].Bellyful()}

	var c Card
	was := b.Cell()
	stood := map[sim.Coord]bool{was: true}
	for t := 0; t < Ticks && len(r.Live) > 0; t++ {
		r.Phase(v, sc)
		v.Now++
		at := b.Cell()
		if at != was {
			c.Cells++
			stood[at] = true
			was = at
		}
		// ... and a count of the ticks spent over water or off the plate ...
	}

	c.Grams = r.Ate
	c.Left, _ = v.Standing()
	c.Steps = r.Steps
	c.Stood = len(stood)
	c.Took = w.Took
	c.Bites = w.Took[beast.Bite]
	if b.Dead {
		c.Died = 1
	}
	return c
}

Two lines in there are load-bearing and easy to miss. Roster.Seen is the hook the phase hands its one reading of the world to before it steps anybody, and passing it to beast.Watch is what gives a network-driven creature the same world its eyes got; without it the legality check has nothing to ask about and hands back whatever the top score wanted. And the loop stops the moment the roster empties. A creature whose store runs out is buried and struck off, and a plate with nobody on it has nothing left to measure, so a genome that starves on tick ninety costs the bench ninety ticks and not six hundred.

Now the number. Every one of those trials produces a small pile of counts, and the temptation with a pile of counts is to make a score out of several of them. Grams eaten, plus a little for how long it survived, minus a little for how far it wandered, plus something for the bites it landed. Every one of those extra terms is somebody's opinion about what a good creature is, written into the ruler. So the card carries nine numbers and a tally of the six actions, and the score reads exactly one of them. The single branch in Score is a flag the last section of this page turns on to show what happens when it reads a different one, and it is off in every arena anything else here runs.

▣ Build · stage 4 — nine numbers measured, one of them the score
// internal/gene/arena.go, continued
// Card is everything one genome's run through the arena came to.
//
// Only the first number is the score. The rest are measurements: they
// are printed, they are folded into the digest, and nothing anywhere
// adds any of them to Grams or lets any of them break a tie. A number
// that decides who breeds is a term of the fitness function whether or
// not the code calls it one.
type Card struct {
	Grams float64 // grams taken off standing fodder, over every trial
	Left  float64 // grams still standing when the trials closed
	Bites int     // bites that actually moved grams
	Steps int     // creature-ticks it was stepped
	Cells int     // times it stepped into a cell it was not on last tick
	Stood int     // cells it stood on at least once
	Died  int     // trials whose store ran out before the ticks did
	Wet   int     // creature-ticks spent over the ring of water
	Off   int     // creature-ticks spent past the last cell of the plate
	Took  [beast.Acts]int
}

// Score is the number the arena hands back for a card, and it is grams
// eaten and nothing else.
func (a *Arena) Score(c Card) float64 {
	if a.Restless {
		return float64(c.Cells)
	}
	return c.Grams
}
$ go run ./cmd/score -mode trial
score: genome 43 of the generation, trial by trial

  the row it is stamped out of: 65.7271 grams of body, a store of 1160.6651,
  a bite of 0.2393 grams reaching 3.1559 cells, 0.1823 cells a tick at a sprint
  it costs 0.4599 a tick to stand there and a full store covers 2524 ticks

   trial    ticks    bites        grams    cells    stood  starved
       1      600       84      20.0000        0        1       no
       2      600      120      28.6154       10       11       no
       3      600      252      60.0000        2        3       no
       4      600      296      70.8376        1        2       no
     all     2400      752     179.4530       13       17        0

  the score is the grams column and nothing else: 179.4530

  the arena's own books, in grams

  fodder laid out                       1880.000000
  fodder still standing                 1700.547009
  taken off the fodder                   179.452991
  counted onto the score                 179.452991

  the two differ by -6.821e-13, which is the last bits of the adding

Read the trial-one line and then read the body above it. Eighty-four bites and exactly 20.0000 grams: this creature stripped one stand of scrub to nothing and then had nothing left inside its reach, because the mouth check refuses a bite when no cell within reach is holding any tissue. Trials three and four went better, and the total is 179.4530 grams out of a larder of 1,880. The last block is the arena closing its own books: the fodder that left the ground and the grams counted onto the score are the same number to within the last bits of the adding, and they have to be, since nothing on this plate manufactures a gram or destroys one.

The interesting thing about genome 43 is what it is. It has a body of sixty-six grams, a store nearly three times the founding row's, a bite of a quarter of a gram, and a top speed of 0.1823 cells a tick, which is under half the row's own sprint. It is slow. It won this generation by standing more or less still with a reach of three and a bit cells and eating everything it could touch.

∑ Math Interlude — the most a genome could have scored, and how long it had

Two pieces of arithmetic bound every number on that table, and both of them can be worked out before any code runs.

The first is the ceiling. A bite occupies two ticks: the tick it is taken on and one more before the creature may choose again. So six hundred ticks hold at most three hundred bites, and a bite takes at most one bite's worth of grams off the plant. For genome 43 a bite is 0.2393 grams, so its ceiling for one trial is

most = (T ÷ tb) × B = (600 ÷ 2) × 0.2393 = 71.79 grams

and 287.16 over the four. It scored 179.4530, which is 62 percent of a life spent with its mouth actually on something. For the founding row's own bite of 0.25 grams the ceiling is 75.00 grams a trial and 300 over the four, and the largest bite this world's ranges allow, a factor of 4.00, puts it at 300 a trial. So no genome anywhere in this arena can score above 1,200, and the best of the sixty-four managed 179.4530.

The second is the clock. A standing body is charged its bulk times its basal rate every tick whatever it does, so a creature that never eats has

ticks = F ÷ (Bulk × Basal)

before its store hits nothing. Genome 43's store over its bill is the 2,524 ticks the run prints, four times as long as a trial lasts, so it was never in any danger of starving and the table says so: nought trials of four. That is an upper bound and not a promise. Moving costs on top of standing, and the charge for moving goes up with the square of the speed, so a creature that sprints spends its clock much faster than this number says. Exercise 1 at the end of the page follows one whose clock reads 930 ticks and which lasted 154 of its first trial's six hundred.

Put the two together and the arena has a comfortable middle. A trial is long enough that a creature has to keep finding food, and short enough that a genome cannot win by sitting perfectly still on a full store for a year. Both of those are properties of the numbers 600, 2 and 24, and both were checked with this arithmetic before the bench was ever run.

Tticks in one trial; 600 here
tbticks one bite occupies before the creature may choose again; 2 here
Bgrams one bite takes off a stand, for this creature
mostthe grams a creature could take in one trial if it never did anything else
Fenergy units this creature's store holds when it is full
Bulk × Basalthe energy a standing body is charged every tick, whatever it does
tickshow long a full store lasts a creature that never moves and never eats
a ÷ ba divided by b

The scored population

One genome measured against a fixed bench is a number. Sixty-four of them measured against the same bench is a population, and a population is the thing the rest of this volume acts on. The sixty-four come off stream 15, which nothing else in this world reads, and every gene of every one of them is drawn evenly over that gene's own range.

▣ Build · stage 5 — a generation drawn out of nothing
// internal/gene/arena.go, continued
// Drawn is one opening generation: n genomes off stream 15, every gene
// of every one of them drawn evenly over its own range.
//
// A genome costs Len() draws and always Len(), for the same reason a
// mutation pass costs twice that: the sixty-fourth genome of a
// generation has to be the sixty-fourth genome whatever the first
// sixty-three came out as.
func Drawn(seed uint64, n int) []*Genome {
	s := rand.New(rand.NewPCG(seed, StreamDrawn))
	pop := make([]*Genome, n)
	for i := range pop {
		g := Identity()
		for j, m := 0, g.Len(); j < m; j++ {
			sp := g.Span(j)
			g.Put(j, sp.Lo+s.Float64()*sp.Wide())
		}
		pop[i] = g
	}
	return pop
}

These are not the founders The Hollow opens with. A founder there has all ten body factors at 1.00 and only its brain drawn, and that is what keeps the valley's opening invariant standing. An arena genome has all three hundred and ninety-five of its numbers drawn, bodies included, so the generation holds creatures with ten-gram bodies and creatures with a hundred and sixty, creatures that see six cells and creatures that see twenty-four. That is on purpose. A bench whose candidates all have the same body can only ever compare controllers, and the question this volume asks is what a whole animal is worth.

$ go run ./cmd/score -mode spread | head -15
score: 64 genomes, 4 trials of 600 ticks each, scored on grams eaten

  the ten best of a generation nothing has selected

    rank   genome        grams    bites    ticks    cells  starved
       1       43     179.4530      752     2400       13        0
       2       27     140.0000      273      844       12        4
       3       46     140.0000      175     1742       13        4
       4       12     101.9936      111     1947        0        1
       5       61     100.0000      405     1584       27        3
       6       19      97.9590      145      987        0        3
       7       62      78.6864      113     1005        2        3
       8       29      60.0000      369     1701        9        2
       9        0      56.0099       70     1889       23        1
      10        4      41.5353      110      354       70        4

The cells column of that table is the surprise. It is how many times the creature stepped into a cell it was not standing on the tick before, added over all four trials, and the whole top ten is in single or low double figures. Genome 12 and genome 19 never changed cells at all, in any of their four trials, and between them they ate two hundred grams. The best creature in a generation nothing has selected is a creature that stands where it was put and eats what it can reach.

That is a fact about this arena and not a law of biology, and the reason is in the Reach gene. A reach of one cell is the cell underfoot and the eight touching it; a reach of four cells, which the body range allows, is eighty-one cells. Genome 43 has a reach of 3.1559, so its mouth covers forty-nine cells of ground without the legs doing anything, and at roughly one stand of fodder in twenty cells that is two or three stands it can strip without moving. Standing still is cheap and moving is charged for. Nothing in this arena had to be told that.

$ go run ./cmd/score -mode spread | tail -19
  the spread

  the best of them                           179.4530
  the genome a quarter of the way down        40.0000
  the middle one                               4.8405
  three quarters of the way down               0.0000
  the worst of them                            0.0000
  the average                                 24.5807
  genomes that ate anything at all                 38
  genomes that scored nothing whatever             26

  grams the generation ate between them     1573.1642
  grams of fodder it was offered          120320.0000
  the share of the larder it found              1.31%
  trials that ended in a store at nothing          129
  creature-ticks over the ring of water          1885
  creature-ticks past the last cell                 0

  digest of the scored generation      e3cd4c94f2fa1612

Twenty-six of the sixty-four scored nothing whatever. Half the generation is at 4.8405 grams or below. The best of them is seven times the average and infinitely better than half its cohort, and between the sixty-four of them they found 1.31 percent of the food they were offered. That is what a population looks like before anything has selected anything: mostly useless, with a long thin tail of things that happen to work. A hundred and twenty-nine of the two hundred and fifty-six trials ended with a store at nothing.

The last two lines before the digest are the plate reporting on itself. One thousand eight hundred and eighty-five creature-ticks were spent standing over the ring of water, which is momentum finishing a step the check had already refused: an animal cannot choose to run at the ring, and one already moving fast enough carries over the edge of the soil anyway. Nought creature-ticks were spent past the last cell of the plate, across all sixty-four genomes and all two hundred and fifty-six trials. The ring holds.

Splitting the work without moving the answer

Sixty-four genomes times four trials is two hundred and fifty-six runs of six hundred ticks, and there is no reason for a machine with eight cores to do them one after another. The creature phase already splits its fan casting across goroutines on the argument that nothing one fan writes is anything another fan reads. The arena has that same argument available and a much stronger version of it: a trial builds its own valley, its own bed, its own roster and its own creature, reads a layout nobody writes to, and draws no random number at all. Two genomes being scored at the same time have literally nothing in common except the layouts they are both reading.

▣ Build · stage 6 — the work goes out in stripes, the numbers come back by index
// internal/gene/arena.go, continued
// Generation scores a whole population and hands back one card per
// genome, in population order, whatever order the work was done in.
func (a *Arena) Generation(pop []*Genome) []Card {
	cards := make([]Card, len(pop))
	if a.Crew <= 1 || len(pop) < 2*a.Crew {
		for i, g := range pop {
			cards[i] = a.Run(g)
		}
		return cards
	}
	var wg sync.WaitGroup
	for k := 0; k < a.Crew; k++ {
		wg.Add(1)
		go func(k int) {
			defer wg.Done()
			for i := k; i < len(pop); i += a.Crew {
				cards[i] = a.Run(pop[i])
			}
		}(k)
	}
	wg.Wait()
	return cards
}

// Fold is the sixteen hex digits one scored generation closes on: every
// number of every card, in population order.
//
// The order is the point. The cards may be filled in by eight
// goroutines in whatever order the machine felt like finishing them,
// and this walks them by index afterwards, so the digest is a fact
// about the generation and not about the scheduling.
func Fold(cards []Card) string {
	h := sha256.New()
	for _, c := range cards {
		put(h, math.Float64bits(c.Grams))
		// ... and every other number on the card ...
	}
	return fmt.Sprintf("%x", h.Sum(nil)[:8])
}
$ go run ./cmd/score -mode split
score: one generation of 64 scored on 1, 2, 4, 8 and 16 goroutines

  goroutines genomes a stripe               digest      holds
           1               64     e3cd4c94f2fa1612  the first
           2               32     e3cd4c94f2fa1612        yes
           4               16     e3cd4c94f2fa1612        yes
           8                8     e3cd4c94f2fa1612        yes
          16                4     e3cd4c94f2fa1612        yes

  the work went out in five different ways and came back one number

Two decisions in that listing are what make the five lines agree. The first is that a goroutine writes cards[i] and touches nothing else on the slice, so the answer for genome 17 is written into slot 17 whether it was finished first or last. The second is that Fold walks the cards by index, after every goroutine has finished, so the digest is built in population order even though the work was not done in it. Get either one wrong and the arena becomes a bench whose answer depends on the machine it ran on, which is not a bench.

The stripes are interleaved, genome k and k plus the crew size and so on, and not cut into blocks of eight. The cost of scoring a genome varies enormously here: genome 43 was stepped 2,400 times and genome 4 was stepped 354, so one genome can be nearly seven times the work of another. Interleaving spreads the cheap and the dear over every goroutine, which is the same reasoning the fan casting was cut up on. On the machine this was written on, an eight-core Ryzen 7 3700X running Go 1.26 on Linux, a generation takes about 280 milliseconds on one goroutine and about 70 on eight; yours will differ, and the sixteen characters will not.

How sixty-four genomes become sixteen characters Three boxes across the top, joined by arrows. The first holds the population, genome 0 through genome 63, drawn off stream 15 at 395 draws each. The second holds the four trials, each with the stands of fodder it was laid out with, drawn once off stream 21. The third holds one card per genome: grams eaten first, then bites, ticks and cells, with a note that only the first of them is the score. An arrow runs down into a wide band saying the work goes out in stripes across one, two, four, eight or sixteen goroutines, that goroutine k takes genomes k, k plus the crew size and so on, and that no two of them touch the same genome, the same valley or the same stream. A second arrow runs down into a final band: the cards are folded by index, 0 to 63, in whatever order they were filled in, and the digest that comes out, e3cd4c94f2fa1612, is the same sixteen characters on one goroutine and on sixteen. HOW SIXTY-FOUR GENOMES BECOME SIXTEEN CHARACTERS THE POPULATION THE FOUR TRIALS ONE CARD EACH genome 0 genome 1 ... genome 63 trial 1 23 stands trial 2 24 stands trial 3 23 stands trial 4 24 stands grams eaten bites, ticks cells, stood starved, and more 395 draws each, off stream 15 104 draws in all, off stream 21 and no draws in here THE WORK GOES OUT IN STRIPES: 1, 2, 4, 8 OR 16 GOROUTINES goroutine k takes genomes k, k+crew, k+2xcrew, and writes only those cards no two of them touch the same genome, the same valley or the same stream e3cd4c94f2fa1612 folded by index, 0 to 63, whatever order they were filled in
Figure 59.1 — where the determinism comes from. The two streams are spent before any scoring starts and nothing draws afterwards, so the middle band can be as wide as the machine allows. The bottom band is the other half: the cards are read back by index, so the digest cannot tell how many goroutines filled them in.

Five properties of the arena are the kind that break silently, so they are tests instead of paragraphs. The first is the one everything else rests on.

$ go test ./internal/gene/ -run 'ScoringAGenome|EveryGenomeMeets|SplittingTheArena|TheLarderAccounts|TheScoreIsGrams' -v
=== RUN   TestScoringAGenomeDrawsNoRandomNumber
--- PASS: TestScoringAGenomeDrawsNoRandomNumber (0.03s)
=== RUN   TestEveryGenomeMeetsTheSameFourTrials
--- PASS: TestEveryGenomeMeetsTheSameFourTrials (0.04s)
=== RUN   TestSplittingTheArenaDoesNotMoveItsDigest
--- PASS: TestSplittingTheArenaDoesNotMoveItsDigest (0.09s)
=== RUN   TestTheLarderAccountsForEveryGramEaten
--- PASS: TestTheLarderAccountsForEveryGramEaten (0.02s)
=== RUN   TestTheScoreIsGramsAndNothingElse
--- PASS: TestTheScoreIsGramsAndNothingElse (0.00s)
PASS
ok  	theworld/internal/gene	0.190s

The first one scores one genome on its own, scores the whole generation, and then scores that same genome on its own again, demanding all three cards be identical. If any part of the arena were quietly reading a stream, the third card would be taken from further along that stream than the first and would differ. The second checks that a whole generation leaves the four layouts exactly where it found them, and that two arenas built from one seed agree about all four. The fifth is the smallest and the one the volume's argument needs most: a card with fewer grams and far more of everything else has to score lower, and two cards with the same grams and nothing else in common have to score the same.

What the score measures

Everything above rests on one sentence that nobody has defended yet: grams eaten is the score. Here is what happens when a different sentence is chosen, and it is a run and not an argument.

The alternative is a reasonable one. A creature that covers ground is exploring, and an animal that explores will find food that an animal sitting still never sees, so the cells a creature stepped into looks like a proxy for something anybody would want. It has the advantage of being easier to measure than eating: the counter for it is already on the card. The arena keeps it behind a flag, so what it buys can be run.

⚠ Worked failure — a score that pays for movement gets movement
// internal/gene/arena.go — inside type Arena struct
	// Restless is the score as it is tempting to write it: the cells a
	// creature stepped into, counted instead of the grams it ate. It is
	// kept behind a flag so what a badly chosen score buys can be run
	// instead of described, and it is false in every arena this book
	// ships.
	Restless bool
$ go run ./cmd/score -mode hack
score: the same 64 genomes, scored on cells stepped into

    rank   genome    cells    stood        grams    bites   on grams
       1        4       70       74      41.5353      110         10
       2       25       62       56      20.0000      212         23
       3       41       53       53       0.0000        0         55
       4       59       50       53       0.0000        0         63
       5       48       38       42      40.0000      358         12
       6       51       31       31       0.0000        0         57
       7       16       29       33       0.0000        0         46
       8       31       28       32      14.8106       22         29
       9       61       27       29     100.0000      405          5
      10        0       23       26      56.0099       70          9

  4 of those ten never took a bite
  the genome this score puts first is 4, which starved in 4 of its 4 trials
  and comes 10 of 64 on grams eaten

  and here is what the score is really paying for: genome 51

  its place on this score                           6
  its place on grams eaten                         57
  grams it ate in 2400 ticks                   0.0000
  bites it took                                     0
  turns left                                     1147
  turns right                                    1167
  walks and sprints between them                   86
  cells it stood on, of 484 a trial                31
  cells it stepped into                            31

The same sixty-four genomes, the same four trials, the same two hundred and fifty-six runs. Only the number at the end changed, and the board is a different board. The creature this score puts first, genome 4, ran its store down to nothing in every one of its four trials, and it ate 41.5353 grams, which puts it tenth on the score that was replaced. Put it through -mode trial -genome 4 and the reason is a sprint of 1.5883 cells a tick and four trials that lasted 154, 128, 24 and 48 ticks of six hundred. Four of the ten best under this score never took a single bite in two thousand four hundred ticks.

The block at the bottom is the one to sit with. Genome 51 is sixth of sixty-four here and fifty-seventh on grams, and look at how it earned that. One thousand one hundred and forty-seven turns to the left, one thousand one hundred and sixty-seven to the right, eighty-six walks and sprints between them, and no bites at all. It turned on the spot for two thousand four hundred ticks and drifted across thirty-one cell boundaries doing it, out of the four hundred and eighty-four cells a trial offers. It went nowhere. The score paid it anyway, because the score does not know the difference between covering ground and crossing a line.

The last thing to notice is that genome 51 could have eaten very well. Run it through the arena and look at the body it was drawn.

$ go run ./cmd/score -mode trial -genome 51
score: genome 51 of the generation, trial by trial

  the row it is stamped out of: 25.4443 grams of body, a store of 1396.0012,
  a bite of 0.7824 grams reaching 1.1537 cells, 0.5480 cells a tick at a sprint
  it costs 0.3141 a tick to stand there and a full store covers 4444 ticks

   trial    ticks    bites        grams    cells    stood  starved
       1      600        0       0.0000        0        1       no
       2      600        0       0.0000       21       19       no
       3      600        0       0.0000        9        9       no
       4      600        0       0.0000        1        2       no
     all     2400        0       0.0000       31       31        0

  the score is the grams column and nothing else: 0.0000

  the arena's own books, in grams

  fodder laid out                       1880.000000
  fodder still standing                 1880.000000
  taken off the fodder                     0.000000
  counted onto the score                   0.000000

  the two are the same number to the bit, which is what a larder nothing grows does

Twenty-five grams of body against a store of nearly fourteen hundred, a bite of 0.7824 grams, which is three times the founding row's, and a full store that covers four thousand four hundred ticks. It is a cheap body carrying a large store and a wide mouth, and it survived all four trials without difficulty. Its controller happened to come out wired so that the two turn entries beat the other four on almost every tick, and under a score that counts boundaries that made it the sixth best creature in the world. The fodder ledger at the bottom closes on exactly the number it opened on: not one gram left the larder in four minutes of world time.

The reasoning from symptom to cause is short and it does not involve a bug. The arena did exactly what it was told. It was told that a good creature is one that steps into cells, and it found the cheapest creature in the population that steps into cells, which is one that turns round on the same few of them. The mistake was made when the sentence was written, and no amount of careful implementation underneath a wrong sentence produces a right answer. That is what makes a score dangerous in a way a broken sensor is not: a broken sensor gives an answer nobody believes, and a wrong score gives an answer everybody does.

Why stable arenas isolate genomes

Strip the creatures out and there are two separate ideas on this page, and they fail in two different ways.

The first is the bench. A benchmark is a copy of the world with everything held still except the thing being measured, and the whole craft of it is deciding what "held still" means. Here it meant the ground, the water, the light, the calendar, the food, the starting cell and the starting heading, all of them fixed in advance and written down. The test of whether that worked is a run: score the same genome twice, in two different places in the work, and get the same number both times. The test at the top of the run above is that property written as code.

The mechanism that buys it is narrower than it looks: every random number the bench needs is drawn before any measuring starts, and turned into data. That single move is what lets the work go out across sixteen goroutines without the answer moving, because two things that draw from a shared sequence are two things whose answers depend on which of them got there first. It is also what makes the arena replayable, comparable and debuggable at the same time, and none of those three had to be arranged separately.

The second idea is the score, and it is the one with teeth. A score is a sentence about what you want, written in a language that cannot express most of what you want. Every term in it is a claim, and the search does not read the claim charitably. It reads it exactly. Ask for cells stepped into and you get a creature that spins in place, because spinning is the cheapest way to satisfy the letter of what was asked. This is not a fact about creatures or about networks. It is what happens whenever the number being optimised is a stand-in for something that could not be measured directly, and the gap between the stand-in and the real thing is where everything goes wrong.

Grams eaten is a much better sentence and it is a stand-in all the same. What anybody actually wants out of this valley is a creature that keeps a lineage going, and grams eaten is a guess that eating is how that is done. The guess is a good one and it is still a guess, made by a person, written into a bench, and imposed on every creature scored against it. Nothing in the arena can tell you whether it was the right guess, because the arena cannot see past its own score.

✓ Checkpoint — the arena, the score, and the order the work goes out in
  • You can name the things the arena switches off and say why the valley's own tick is never called at all, instead of called with the weather set to nothing.
  • You can say how many numbers one trial layout costs off stream 21, why that count does not depend on where the cells landed, and what would break if a layout were redrawn for each genome.
  • Handed a creature's bite size, You can work out the most it could possibly score in one trial, and say what part of the six hundred ticks that ceiling assumes.
  • You can explain why the same generation folds to the same sixteen characters on one goroutine and on sixteen, and name the two lines of code the claim rests on.
  • You can point at the ring of open water and say which existing rule makes it a wall, and why creature-ticks over it are not zero while creature-ticks past it are.
  • Shown a score that counts cells stepped into, You can predict what kind of creature tops it and explain the prediction without running anything.
⚡ Exercises — try first, then reveal
Exercise 1 — the creature that ran out of clock. Genome 4 is tenth on grams and first on cells. Work out from its body how long its store would have lasted it with nothing coming in, then say why it did not last anything like that. Check with go run ./cmd/score -mode trial -genome 4.

The run prints a body of 61.8175 grams costing 0.5793 a tick, against a store of 538.4960, so the standing clock is 538.4960 ÷ 0.5793 = 930 ticks. It had enough store to finish a six-hundred-tick trial doing nothing. It finished none of them: 154 ticks, 128, 24 and 48, for 354 out of a possible 2,400.

The gap is the legs. It sprints at 1.5883 cells a tick, and the travel charge goes up with the square of the speed, so a tick spent at full sprint costs it many times the tick a standing body pays for. The standing clock is an upper bound on a creature that never moves, and this one moved constantly, which is exactly why it topped a score that counts movement and starved in every trial.

Exercise 2 — a bench that only asks one question. Run the generation with a single trial, go run ./cmd/score -mode spread -trials 1, and compare the top ten with the four-trial board. Say what changed and why the arena runs four.

The board falls apart. Eight of the ten best are tied at exactly 20.0000 grams, which is one stand of scrub stripped and no more, and the winner is genome 37, which does not appear anywhere in the four-trial top ten. Genome 43, which wins the four-trial board by a distance, comes tenth on a single trial with 20.0000 grams, because its one trial happened to be the trial where a single stand was all it could get to. One trial is one starting cell and one heading, so it ranks placement as much as it ranks the genome.

Now go the other way with -trials 8. The top ten holds nine of the same genomes in a slightly different order, and the scores are close to double the four-trial ones because the larder was double. Eight trials cost twice the machine time and moved the answer hardly at all, which is what steadiness looks like when it has already arrived. Repeated trials buy their steadiness slowly: the wobble in an average of n runs falls with the square root of n and not with n, so the fourth trial is worth much more than the eighth.

Exercise 3 — write a score with an opinion in it. Add a term to Arena.Score that pays a genome one gram for every trial it survived, rescore the generation, and say which genome the change promotes and what you have just told the arena to want.

One line does it: return c.Grams + float64(len(a.Trials)-c.Died). The effect is small at the top, because four grams against 179.4530 is noise, and large below the middle, where half the board is packed into the 4.8405 grams between the median and nothing. Genome 12, which ate 101.9936 and starved in one trial, gains three; genome 27, which ate 140.0000 and starved in all four, gains nothing.

The promotion to look at is at the bottom. Genome 51, which ate nothing whatever and turned on the spot for all four trials, now scores 4.00 and beats genome 14, which found 2.3110 grams and starved in every trial; check both with -mode trial. The term was added to reward survival and what it actually rewards is doing nothing carefully. It is four grams of somebody's opinion, and the search will take it as literally as it took cells stepped into. Put the line back.