A Seventh Thing To Do
The action table width
Every animal that has ever walked in The Hollow has picked one of six things to do with a tick: rest, walk, sprint, turn left, turn right, or bite. To get a second eater level, the table needs a seventh entry.
The width of the action table and the width of the frozen controller are two
numbers. beast.Acts is 7, and it is how many entries the table holds.
beast.Fixed is 6, and it is how many scores the three-row network hands back.
The network keeps its 24-12-6 shape and its 378 weights, every site that builds one says
Fixed, and an animal drawn before there was anything to hunt has no output for
hunting.
The old table was written down once, in one file, and every creature driver read it: a hand-written rule, a three-row network, and a wiring that grew its own nodes. Six entries meant six prices and six preconditions.
A valley of animals that can only eat plants has one eater level. Everything alive takes its energy off standing tissue. Everything dead becomes litter. No creature has a reason to look at another creature except to walk round it.
A bite is a mouth on a plant. Its rule asks whether standing tissue is inside reach, its price is the mouthful crossing that reach, and its effect is grams off a stand. Pointing it at a body would need different answers to questions the entry has already answered.
Appending one entry carries the arithmetic that made the last volume refuse the change. The frozen controller is twenty-four numbers in, twelve neurons in a middle row, and one number out for each entry of the table. Write the entry count as a constant and use it for both, and widening the table from six to seven changes the network from 24-12-6 to 24-12-7.
Count the weights: 24 × 12 for the middle row, 12 biases, 12 × 7 for the output row, 7 more biases. That is 391 where it was 378. Every founding animal in every run this book has printed draws 391 numbers off the brain stream instead of 378.
The second animal founded reads a brain starting thirteen numbers later than the one it read before, and the third reads one twenty-six numbers later. The flat genome goes from 395 numbers to 408. Every digest, every death tick, every closing grams total in three volumes moves, including the ones the last volume opened by proving had not.
That price is not payable, and it measures a mistake buried in the code since the wiring chapter: two different facts written down as one number because they happened, for two volumes, to be equal.
That split states something instead of dodging something: a frozen controller is frozen including its output count. The last volume made exactly this argument at the other end of the same network: the sensor row stayed twenty-four numbers wide because a link carries the number of the node it comes from, so an input added after the first genome exists renumbers nothing and matches nothing. The wider row that the evolved wiring reads lives in a different package and the body never fills it in. This page runs the same argument at the output end.
By the end of the page the table holds seven entries. The seventh is called strike. It has a price, a rule of its own, and a fourth refusal value to go with the three the check already had.
It moves nothing whatever, because there is nothing in this valley an animal is allowed to
hit. The seventh bench and the final runs say that none of this touched older
behavior: a thousand years of ground, a herd of nineteen animals that starve on the ticks
they have always starved on, and a scored generation folding to the same sixteen
characters.
Appending the strike entry
Appended is the operative word. The entries are named by an iota block and
indexed by everything that ranks them, so rest is nought and bite is five in every run this
book has ever folded a digest out of. Insert the new entry anywhere but the end and every
one of those rankings is ranking something else. It goes at index six and the six in front
of it are untouched to the byte in name, ticks, price and rule.
The price is the one part that needed a decision, and it turned out to need no new
arithmetic. This world prices movement one way: the mass being moved, times the square of
the speed it is moved at, at the row's price for a unit of work. A bite is priced that way
already, with the mouth's own two numbers in it, Bite grams carried over
Reach cells in a tick. A strike is the same charge with the two numbers a
whole animal puts into it: Bulk grams at Top cells a tick. It
occupies two ticks, like a bite, so an animal that throws itself at something is not free
to do anything else on the tick after.
The rule needs one number the creature row did not carry. A strike is allowed against a
living creature standing below the striker in the valley's eating order, and nothing else,
so a row has to say where it stands. beast.Kind gains a single integer field
for it. Plants are level nought and are not creatures; the browser is level one. Nothing
else on the row moves, and the ten factors a genome multiplies are still ten.
// internal/beast/act.go
const (
Rest Act = iota
Walk
Sprint
Left
Right
Bite
Strike
)
// Acts is how many entries the table has, and Fixed is how many of
// them the frozen controller can name. They are two different numbers
// and they were only ever spelled the same.
const (
Acts = 7
Fixed = 6
)
var Table = [Acts]Move{
Rest: {Name: "rest", Ticks: 1, Price: nothing, Allow: always, Needs: "nothing"},
Walk: {Name: "walk", Ticks: 1, Price: amble, Allow: ground, Needs: "the cell ahead is not open water"},
Sprint: {Name: "sprint", Ticks: 1, Price: dash, Allow: ground, Needs: "the cell ahead is not open water"},
Left: {Name: "turn left", Ticks: 1, Price: Kind.Wheel, Allow: always, Needs: "nothing"},
Right: {Name: "turn right", Ticks: 1, Price: Kind.Wheel, Allow: always, Needs: "nothing"},
Bite: {Name: "bite", Ticks: 2, Price: Kind.Chew, Allow: mouth, Needs: "tissue standing inside Reach"},
Strike: {Name: "strike", Ticks: 2, Price: Kind.Lunge, Allow: prey, Needs: "a living creature of a lower level inside Reach"},
}
// Lunge is what one strike costs to throw, as opposed to what it does
// where it lands. It is the same movement charge again, with the two
// numbers a mouth puts into it replaced by the two a whole animal
// does: Bulk grams instead of Bite grams, and Top cells a tick instead
// of Reach. A bite is a jaw crossing a cell; a strike is the animal.
// ...
func (k Kind) Lunge() float64 { return k.Travel(k.Top) }
var (
// ... ErrBroke, ErrNoGround and ErrNoReach, unchanged ...
// ErrNoPrey is a strike with nothing inside reach it may land on.
ErrNoPrey = errors.New("no creature of a lower level inside reach")
)
// internal/beast/beast.go — one field on the row
type Kind struct {
// ... the ten factors, unchanged ...
// where it stands in the valley's eating order. Plants are level
// nought and are not creatures, so the lowest a body can be is
// one. It is the one number on this row that is not a price and
// not a measurement: it is a fact about who may do what to whom.
Trophic int
}
// Ever reports whether an animal stamped from this row could take the
// action on some tick of some valley. It is a question about the row
// and not about the afternoon: the check answers whether a particular
// creature may strike where it is standing now, and this answers
// whether the row it came from could ever strike at all.
// ...
func (k Kind) Ever(a Act) bool {
if a == Strike {
return k.Trophic > 1
}
return true
}
$ go run ./cmd/seventh -mode table
seventh: the action table, and the two numbers that used to be one
beast.Acts 7 entries in the table
beast.Fixed 6 scores the frozen controller hands back
priced for the browser: bulk 40 grams at level 1, work 0.60,
top 0.45 cells a tick, bite 0.25 grams, reach 1 cell
action price ticks scored by what has to be true here
0 rest 0.0000 1 both nothing
1 walk 1.2150 1 both the cell ahead is not open water
2 sprint 4.8600 1 both the cell ahead is not open water
3 turn left 0.4050 1 both nothing
4 turn right 0.4050 1 both nothing
5 bite 0.1500 2 both tissue standing inside Reach
6 strike 4.8600 2 graph only a living creature of a lower level inside Reach
a bite carries 0.25 grams over 1 cell in a tick: 0.60 x 0.25 x 1 x 1 = 0.1500
a strike throws 40 grams at 0.45 cells a tick: 0.60 x 40 x 0.45 x 0.45 = 4.8600
a sprint asks the legs for the same 4.8600 and Step charges what they reached
a strike is charged in apply, in full, on the tick it is thrown
the standing body is charged 0.1600 a tick whatever the action is, so one
strike is 30.3750 ticks of standing still, 32.4000 bites to take,
and 4.8600 mouthfuls of what a bite is worth once it is inside
the browser row can ever take: rest walk sprint turn left turn right bite
and never: strike, because nothing in this valley stands below level 1
The two 4.8600s in that table are the same number and they are not the same bill. A
sprint asks the legs to reach the row's top speed and the table quotes what a tick at
that speed would cost; the step then charges the speed the body actually got to, which
for an animal starting from a standstill is a good deal less. A strike is charged in
apply, at the quoted figure, on the tick it is thrown. Nothing about the
arithmetic distinguishes them. What distinguishes them is where the charge is made, and a
reader who wants a name for it can have this one: a sprint's price is a quote and a
strike's price is an invoice.
The founding row is forty grams of body, six tenths of an energy unit for a unit of movement work, and a top speed of 0.45 of a cell a tick. Multiply the mass by the speed and then by the speed again, and then by the price of a unit of work.
Forty times 0.45 is 18. Eighteen times 0.45 is 8.1. Eight point one times 0.6 is 4.86. That is the whole calculation, and it is the same three multiplications a walk and a sprint have always been.
Now put it against the two other numbers on the row. The standing body is charged four thousandths of an energy unit for every one of its forty grams, every tick, whatever it does: 0.16 a tick. So one strike is 4.86 divided by 0.16, which is 30.375 ticks of doing nothing at all. And a bite takes a quarter of a gram of tissue worth four units a gram, so a mouthful is one energy unit and a strike costs 4.86 mouthfuls to throw. An animal that throws one and gets nothing back has spent about five meals.
The same three lines in symbols. The first two are the same charge with different numbers put into it, and the third is what the answer buys:
strike = Work × Bulk × Top × Top
bite = Work × Bite × Reach × Reach
ticks of rent one strike buys = strike ÷ (Basal × Bulk)
The Bulk cancels in that last one, so the answer is the same 30.375 ticks
for a body of any size at this row's speed and price. A heavier animal pays more for a
strike and more for standing still, in the same proportion.
The rule is the other half of the entry and it is a search. The check has never allocated anything and this does not change that: the walk over the ranking asks it up to seven times a tick, and a rule that built a list would build three thousand of them a tick across a valley. It is the same loop the mouth already uses, pointed at bodies instead of at stands.
// internal/beast/act.go
// Quarry is the body this creature's strike would land on and the
// grams standing in it: of every creature inside Reach at a strictly
// lower trophic level, the one carrying the most.
//
// Ties go to the first creature in roster order, and roster order is
// ascending identity: identities are handed out in founding order and
// never reused, the roster is never re-sorted, and burying the dead
// keeps the order of everything still walking. So the tie is settled
// by comparing two integers, which is the same answer on every machine
// and does not depend on which cell the walk reached first.
func (b *Beast) Quarry(v *View) (*Beast, float64) {
n := int(b.Kind.Reach)
here := b.Cell()
var best *Beast
most := 0.0
for dy := -n; dy <= n; dy++ {
for dx := -n; dx <= n; dx++ {
for _, o := range v.Crowd(here.Offset(dx, dy)) {
if o == b || o.Dead || o.Kind.Trophic >= b.Kind.Trophic {
continue
}
if best != nil && (o.Kind.Bulk < most ||
(o.Kind.Bulk == most && o.ID > best.ID)) {
continue
}
best, most = o, o.Kind.Bulk
}
}
}
if best == nil {
return nil, 0
}
return best, most
}
// prey refuses a strike with nothing it may land on. A row that can
// never strike at all is refused here every time, for the same reason
// and by the same search: it finds nothing below its own level because
// there is nothing below its own level to find.
func prey(b *Beast, v *View) error {
if q, _ := b.Quarry(v); q == nil {
return ErrNoPrey
}
return nil
}
$ go run ./cmd/seventh -mode check
seventh: the check asked all 7, on a valley with one kind of animal in it
creature 1 on 9,5 at level 1, 400.0000 in the store, 19 others standing
the richest cell inside its reach is 8,6 at 169.2321 grams of tissue
3 creatures stand inside its reach and none of them is below it
action price ticks what the check says
rest 0.0000 1 allowed
walk 1.2150 1 allowed
sprint 4.8600 1 allowed
turn left 0.4050 1 allowed
turn right 0.4050 1 allowed
bite 0.1500 2 allowed
strike 4.8600 2 creature 1 at 9,5 cannot strike: no creature of a lower level inside reach
the strike, read three ways
as a sentence creature 1 at 9,5 cannot strike: no creature of a lower level inside reach
as a rule errors.Is(err, beast.ErrNoPrey) is true
as numbers it wanted 0.0000 and found 0.0000: this rule compares none
and the same creature with 4.0000 in the store, which is under the price
as a sentence creature 1 at 9,5 cannot strike: the store cannot pay for it
the store is asked first, so a broke animal is refused for being broke
One name in that search has not been met before. View.Crowd hands back every
creature the phase's index has on one cell, in roster order, and it is the animal-shaped
twin of the View.Stand a mouth already walks: the same index, built once at
the top of the phase, asked for bodies instead of for tissue. It is the bucket itself and
not a copy, so a caller reads it between one tick and the next and never keeps it, and it
holds anything that was standing there when the phase opened — including a creature
written off earlier in this phase and not yet buried, which is why the walk asks
o.Dead rather than trusting the bucket.
Read the third line of that run twice. Three creatures are standing close enough to hit and the strike is still refused, and the refusal says bodies are present but none sits on the lower trophic level. That distinction is the entire trophic rule, and it is doing its work on a page where there is exactly one kind of animal in the world.
The refusal carries no numbers, and that is decided rather than forgotten. The other three rules each compare two quantities and can say how far short the answer fell: what the price was against what the store held, what the mouth wanted against what was standing there. A search with no quarry has no such pair, and writing a nought into those fields to keep the shape tidy would be inventing a comparison that was never made. The struct has always said that a rule comparing no numbers leaves them at nothing, and this is the first rule to take it up on that.
The last two lines of the run are the ordering. The store is asked before the entry's own test is, for every entry, because it is one subtraction and all seven are subject to it. An animal with four units left is refused for being broke and never gets as far as the search.
The Fixed and Acts constants
Every line in the module that names the entry count now has to be sorted into one of two
piles, and the sorting rule is one sentence: anything that builds or ranks the frozen
network says Fixed, and anything that is about the table itself says
Acts. In the four packages that make up the simulation the answer comes
to one new constant and eleven lines that read it. Everything else goes on saying the
table's own width and goes on meaning it.
The driver that wires the frozen network to a creature holds a slice of six numbers, made once when the brain was wired and reused every tick. Its ranking walk goes over those six. Its two counters, the one that records what the top score asked for and the one that records what the creature was left holding, are six long, because a run that counts what the herd took has six columns and would otherwise grow a seventh full of zeros. Its constructor refuses a network of any other output count, and the sentence it panics with now names the frozen controller instead of the table, because those are no longer the same claim.
The genome package is where the number does the most damage if it is wrong. Three lines
there decide the size of a MIND block, and all three now read Fixed: the
function that says how many numbers a block holds, the one that draws a founding brain, and
the one that builds a controller back out of a block. The card the arena fills in for one
genome carries a count per entry the creature could name, so it is six wide, and the digest
a scored generation folds down to is unmoved.
The other direction matters as much. The wiring a lineage grows is built at the table's own
width and always was: it joins its inputs to beast.Acts outputs, its driver
ranks all of them, and the mint that names its links counts from the row width plus the
table width. Those lines were already right and none of them changed. A volume that widens
the action table and spends nothing at all on the network package is the seam doing what a
seam is for.
// internal/beast/wits.go
Wanted [Fixed]int // what the top score asked for, whether or not it happened
Took [Fixed]int // what the creature was left holding
func NewWits(n mind.Net, b *Beast) *Wits {
if n.In != Inputs || n.Out != Fixed {
panic(fmt.Sprintf(
"a %d-%d-%d network cannot drive a creature: the sensor row is %d numbers and the frozen controller is %d wide",
n.In, n.Hid, n.Out, Inputs, Fixed))
}
// ...
}
// internal/gene/gene.go
//
// beast.Fixed and not beast.Acts, and it is the one-line difference
// between a volume that could widen the action table and a volume that
// could not. The fixed shape is 24-12-6 and it is fixed including the
// six: 24x12 + 12 + 12x6 + 6 = 378. Spell this beast.Acts and every
// founding brain in every run this book has ever printed draws 391
// numbers off stream 14 instead of 378, in a different flat order, and
// there is no digest anywhere in the book that survives it.
func Weights() int { return mind.Weights(beast.Inputs, Hidden, beast.Fixed) }
// internal/gene/sense.go — the founding cut, now at both ends
func Shut(g *Genome, k beast.Kind) int {
w := g.Wire
if w == nil {
return 0
}
cut := 0
for i := len(w.Links) - 1; i >= 0; i-- {
l := w.Links[i]
no := l.From >= beast.Inputs
if !no && l.To >= w.In && l.To < w.In+w.Out {
no = !k.Ever(beast.Act(l.To - w.In))
}
if no {
w.Cut(i)
cut++
}
}
return cut
}
Shut is the interesting one. It already cut every link leaving a sensor
channel the body does not fill in, and the argument for that was about upkeep: a link out
of the litter channel is an animal that knows what is lying under it, and an animal that
knows that has an organ to keep alive. Run the same argument at the output end and it
comes out the same way. The opening wiring joins every input to every output, and for a
browser one of those outputs is an entry the check will refuse on every tick of every
life in the lineage. Twenty-four links into it would be twenty-four numbers copied at
every birth, crossed at every crossing and mutated at the usual rate, to produce a score
that is always thrown away. So the founding cut takes them, and the row goes into
Root because the row is the only thing that decides which ones to take.
$ go test ./internal/beast/ -run 'TheTableIsSeven|TheSevenPrices|ABrowserCanNever|AStrikeIsRefused' -v
=== RUN TestTheTableIsSevenAndTheControllerIsSix
--- PASS: TestTheTableIsSevenAndTheControllerIsSix (0.00s)
=== RUN TestTheSevenPricesOffTheBrowserRow
--- PASS: TestTheSevenPricesOffTheBrowserRow (0.00s)
=== RUN TestABrowserCanNeverStrike
--- PASS: TestABrowserCanNeverStrike (0.00s)
=== RUN TestAStrikeIsRefusedWithNothingBelowIt
--- PASS: TestAStrikeIsRefusedWithNothingBelowIt (0.01s)
PASS
ok theworld/internal/beast 0.013s
$ go test ./internal/gene/ -run 'TheFrozenBlockIsStill|AFoundingBrowserHasNoNerve|ARowThatCanStrikeKeeps' -v
=== RUN TestTheFrozenBlockIsStillThreeHundredAndSeventyEight
--- PASS: TestTheFrozenBlockIsStillThreeHundredAndSeventyEight (0.00s)
=== RUN TestAFoundingBrowserHasNoNerveToTheStrike
--- PASS: TestAFoundingBrowserHasNoNerveToTheStrike (0.00s)
=== RUN TestARowThatCanStrikeKeepsThatNerve
--- PASS: TestARowThatCanStrikeKeepsThatNerve (0.00s)
PASS
ok theworld/internal/gene 0.003s
The first of those tests replaces one that used to say the table held six entries and that six was canon. It now pins both numbers and the relation between them, and the two halves fail for different reasons. A table short of seven entries is a world where nothing can hunt. A frozen controller wider than six draws 391 numbers into a block that holds 378, and takes every figure in this book behind it with it.
The generated wiring break
Everything driven by the frozen network runs exactly as it ran. Everything driven by a
wiring does not, and the reason is one line: a graph is built at beast.Acts
outputs and beast.Acts is now seven. This is a break in the run of the book,
it lands here, and the honest thing to do with it is print it rather than describe it.
$ go run ./cmd/seventh -mode widths
seventh: what a wider table does to a wiring, and what it does not
the frozen controller 24-12-6, 378 weights, unmoved
a flat genome 395 numbers, unmoved
the opening graph at 6 out at 7 out
nodes 38 39
links, every input to every output 192 224
biases, one on each output 6 7
pairs a link may join 192 224
MIND genes 198 231
a founding browser, after the cut at 6 out at 7 out
links 144 144
biases 6 7
MIND genes 150 151
genes in the whole genome 167 168
numbers one birth spends 509 512
legal pairs left open 48 80
the browser keeps the same 144 links at both widths, because the links it
loses at the wider table are the 24 it would have had into an output it
can never score. What it gains is one bias on that output: 168 genes against
167, and 512 numbers a birth against 509, because a birth spends three a gene
a row one level higher keeps that output's nerves: 168 links, 192 genes,
584 numbers a birth, and 56 legal pairs left to grow into
pairs a lineage can still grow browser level 2
legal pairs in all 224 224
of those, not yet joined 80 56
and billed for if it grows one 49 49
Both columns of that run are built by the bench, off the same code, at the two widths. The left one is the wiring this world drew while the table had six entries in it. The right one is the same wiring now.
The founding browser is the line to stop on. It carries the same 144 links at both widths, because the twenty-four it would have gained by the table growing are exactly the twenty-four the founding cut takes back out. What it does gain is one bias, on an output node nothing feeds and nothing it can do reads. One gene. That single gene is the whole of the difference between a browser founded here and one founded before the table grew, and it is enough: a birth spends one number choosing which parent a gene came from and two more on the copy's mistakes, so one gene is three numbers, and 509 becomes 512. Three draws a birth, and by the second birth the valley is somewhere else entirely.
So no run of this book that was driven by a wiring reproduces in this module, and none of the counts, digests, curves or pedigrees any of them printed may be set beside a figure from here in either direction. The machinery is unchanged to the line except where this page changes it. The histories are not, and no amount of care about the streams could have made them so.
The last block of the run is a lineage's room to grow. Both rows see the same 224 legal pairs, because the pairs are a fact about the node set and not about the wiring; the browser has 80 of them still unjoined against the level-2 row's 56, and exactly 49 of each are pairs that would leave a channel carrying an organ's bill. A browser founds with more room and the same upkeep waiting behind it.
Which raises the obvious question about the seventh entry in a valley that has nothing to hunt. A founding browser has no link into that output. A birth can add one anyway: the structural operator picks one of the legal pairs at random and joins it if it is not already joined, and some of those pairs arrive at output six. So the thing to do is found a valley of wired browsers, let it breed for a while, and count.
$ go run ./cmd/seventh -mode wild
seventh: 16x12 valley, 25 browsers founded on stream 12, every one of them
driven by a graph the table's own width, 7 wide, and none of them
founded with a nerve into the seventh output
year walking born links into 6 strikes
5 19 175 15 1 7920
9 194 1122 757 15 49328
13 221 1553 1354 48 78365
17 144 1764 1944 63 100580
25 founded, 1764 born, 1645 struck off, 144 still walking after 57150 ticks
1944 links were added over the run and 2032 nodes put in
action wanted handed on share creatures wired to it
rest 1227010 4586541 70.5% 1789 of 1789 that ever lived
walk 92557 20724 0.3% 1789 of 1789 that ever lived
sprint 27152 6213 0.1% 1789 of 1789 that ever lived
turn left 75383 126416 1.9% 1789 of 1789 that ever lived
turn right 84948 338463 5.2% 1789 of 1789 that ever lived
bite 4894741 1424014 21.9% 1789 of 1789 that ever lived
strike 100580 0 0.0% 63 of 1789 that ever lived
wanted is the top score, handed on is what came back out of Pick
6502371 forward passes spent, 1423986 ticks held by an action under way
the check crossed out 5121238 scores inside Pick over the run
the tick itself refused 0 actions, because Pick never hands it one
The strike was the top score 100,580 times and no creature took it once. Both halves of that are the design working. It gets scored at all because a wiring is built at the table's width and puts a number on every output, and for a browser that number is a drawn bias with nothing feeding it, which is sometimes the largest of the seven. It is never taken because the ranking walk asks the check before it settles, the check finds nothing below a browser anywhere in the valley, and the walk falls through to the next score down. The lineage pays nothing for the refusal and gains nothing from the attempt.
Sixty-three of the 1,789 animals that ever lived in that run carried a link into an output they could never score, and not one of the twenty-five founders had one. Nobody put those links there and nothing takes them out. They are a real cost, small and countable: three numbers a birth per gene, carried by about one animal in twenty-eight, spent on arithmetic whose answer is discarded. Cutting them at the founding is what keeps the other 1,726 from paying it too, and there is no mechanism anywhere in this valley that would stop a lineage growing one back. That is a result a run states, not a bug this page fixes.
The last line of the block is a small thing that says the loop is closed properly. The tick refused nothing at all over fifty-seven thousand ticks of a valley, because a driver that ranks and checks never hands the tick an action the tick would refuse. The 5,121,238 crossings-out all happened inside the ranking, where they cost a comparison each and no creature its turn.
That run ends on a line the block above leaves out: 57,150 ticks in 19.843 seconds, 2,880 ticks a second, measured on an eight-core Ryzen 7 3700X and different on your machine. The tick count is arithmetic and is compared like everything else here. The duration and the rate are this machine talking, and no argument on this page rests on either.
Three unchanged runs
Now the claim. Three runs, all of them from before the table grew, and all three still byte-identical here. They were picked because they cover three unrelated parts of the world and because not one of them was written by this page.
Start with the ground, which has nothing to do with creatures at all. The terrarium's own streams carry seed flight, germination, plant death and where a founding generation lands. A valley with an empty creature phase hooked into all three million six hundred thousand ticks of a thousand years has to close on the two digests it closed on before any of this existed.
$ go run ./cmd/books -mode keep -years 1000
books: seed 5, 1000 years, an empty roster hooked into every tick
world df1c117e69bdf157 the terrarium's own, unmoved
census 75c77a67f6f430bb the terrarium's own, unmoved
Three lines out of a run that takes about three and a half minutes. The first digest covers the ground and everything standing on it at the end; the second is folded from a census taken every midsummer of all thousand years, so a valley that arrived at the right answer by a different road fails it. Nothing this page touched goes anywhere near those streams, and the run is here to say so instead of the page asserting it.
Then the animals. Nineteen creatures founded on the creature stream, each stamped out of an identity genome, each handed a brain of its own off the brain stream, and two years of valley with nothing registered into the breeding seam. Every one of them is driven by the frozen network, so every one of them is asking a six-long ranking about a seven-entry table.
$ go run ./cmd/stamp -mode herd
stamp: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams
19 creatures founded on stream 12, each stamped from an identity
genome and given a brain of its own off stream 14
beast.Roster.Born is nil: nothing has registered a breeding pass into it
this run before the genome holds
creatures founded 19 19 yes
creatures struck off 17 17 yes
still walking after 6750 ticks 2 2 yes
first death tick 1044 on 9,5 tick 1044 on 9,5 yes
last death tick 3400 on 11,5 tick 3400 on 11,5 yes
grams in 46567.944597 46567.944597 yes
grams out 46567.944597 46567.944597 yes
the two ledgers differ by -1.455e-11, which is the last bits of the adding
Seventeen animals starve, and they starve on the ticks they have always starved on: the first on tick 1044 standing over cell 9,5 and the last on tick 3400 over 11,5, with nothing at all dying in the four thousand two hundred and fifty-one ticks between that last death and the end of the run. The grams close in and out at 46567.944597. The right-hand column is not a person copying numbers off the left-hand one; those figures are constants in the bench and the word at the end of each line is a comparison the program made. The column is headed for the volume that wrote the bench, which is the point: those constants are two volumes older than this page and nothing on this page was allowed to touch them.
The third is the arena, which is neither the ground nor a valley: sixty-four genomes drawn off their own stream, four trials apiece, scored on grams eaten, with every card folded into one digest. A card carries one column per entry the creature could name, so the width of that fold is the thing this page had to be careful with.
$ go run ./cmd/score -mode spread
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 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
Genome 43 is still the best of the sixty-four at 179.4530 grams, the middle genome still
scores 4.8405, the average is still 24.5807, twenty-six of the sixty-four still eat nothing
whatever, and the whole generation still folds to e3cd4c94f2fa1612. The board
is not a herd and it is not the ground, and that is why it is on the list: it is the one of
the three that would have caught a card that quietly grew a seventh column.
Three runs, one changed foundation, and every figure on all three unmoved. The invariant this volume opens with is therefore stronger than the one before it and not weaker: it now covers a benchmark as well as a herd, and it survives a change to the action table, which is the thing this book had said could not be survived.
The mistake this page exists to prevent takes about four seconds to make. The founding of a creature's brain reads three sizes: the sensor row, the middle row, and the number of things it has to score. That last one has been spelled with the table's own constant since the wiring chapter, and it was correct every day until this one. Search for the table's width, find that line, leave it alone because it looks right.
// internal/gene/gene.go — the founding brain, at the wrong width
func founder(r *rand.Rand) *gene.Genome {
g := gene.Identity()
n := mind.New(beast.Inputs, gene.Hidden, beast.Acts) // 24-12-7, 391 numbers
n.Draw(r)
copy(g.Mind, n.W) // the block holds 378
return g
}
It compiles. It vets clean. The copy is not even wrong in the way a length mismatch
usually is: Go's copy takes the shorter of the two, so 378 of the 391 drawn
numbers go into the block and thirteen are dropped on the floor with no complaint. Every
test in the module passes. Then run the herd.
$ go run ./cmd/seventh -mode herd -muddled
seventh: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams
19 creatures founded on stream 12, each stamped from an identity
genome and given a brain of its own off stream 14
every brain was drawn 24-12-7, 391 weights, 7429 numbers off 14 in all
the controller was built at beast.Acts and the creature runs on beast.Fixed
this run before the seventh holds
creatures founded 19 19 yes
creatures struck off 18 17 NO
still walking after 6750 ticks 1 2 NO
first death tick 1601 on 7,3 tick 1044 on 9,5 NO
last death tick 4636 on 6,2 tick 3400 on 11,5 NO
grams in 39968.800971 46567.944597 NO
grams out 39968.800971 46567.944597 NO
the two ledgers differ by -3.420e-10, which is the last bits of the adding
Every line says NO, and the ledger still closes. That is how this failure presents: it does not produce a broken valley, it produces a different one. Eighteen animals starve instead of seventeen, the first of them on tick 1601 over cell 7,3, and the valley closes on 39968.800971 grams because different animals ate different plants on different ticks for two years.
The first line of the header is where the cause is hiding. The founding spent 7,429 numbers off the brain stream where it spent 7,182 before, and 7429 minus 7182 is 247, which is nineteen founders at thirteen extra numbers each. Look at what that does founder by founder.
$ go run ./cmd/seventh -mode draws
seventh: what a founding brain spends off stream 14
the frozen controller 24-12-6 378 numbers, and a MIND block is 378
built at the table's width 24-12-7 391 numbers, and the copy keeps 378
founder drawn at Fixed drawn at Acts weights the two agree on
1 0..377 0..390 378 of 378
2 378..755 391..781 0 of 378
3 756..1133 782..1172 0 of 378
4 1134..1511 1173..1563 0 of 378
the first animal in the valley is the same animal: the two rows of
weights split at index 300 in both networks and the first 378 numbers off
the stream are the same numbers, so the copy keeps every one of them.
The second animal is not, and the whole of the reason is that the first
one spent 391 numbers where it should have spent 378
The first founder is untouched. Both networks divide their two rows of weights at the same index and scale them by the same two divisors, so the first 378 numbers off the stream are the same 378 numbers and the copy keeps all of them. The second founder is a different animal, and the only reason is that the founder in front of it took thirteen numbers it had no use for.
The rule underneath is the one this book has been under since a founding first drew anything: a seeded generator is a fixed sequence, and a draw is a request for the next number in it, so how many numbers a job takes is part of the job. This is that rule arriving through a constant instead of through a line of code, which is what makes it worth a page. Nobody added a draw. Somebody widened a number that two different jobs were reading, and one of them was not entitled to the new value.
Why interface widths are promises
Take the valley away and what is left is a pattern that turns up wherever something old keeps running against something that has grown. Two things are joined by a seam. The seam has a size to it: how many arguments, how many columns, how many outputs, how many bytes. One side of the seam gets wider. The question is what the other side was promised.
The failure mode is always the same, and it is quiet: one constant was carrying two meanings and only one of them was allowed to move. Here the constant meant both how many things exist and how many things this machine can express an opinion about. Those were equal for two volumes and there was no way to tell they were different, because nothing had ever made them disagree. The moment something did, every site reading the constant had to be sorted into one of two piles. Across the four simulation packages eleven lines turned out to be about the frozen machine: seven in the driver that ranks its scores, three in the genome package, one on the arena's card. Every other mention in the module was about the table and needed nothing done to it.
The fix has a general form, and it needs none of this world's nouns. When a shared constant is about to be widened, ask of every reader whether it is reading a fact about the world or a fact about a fixed thing that was built when the world was narrower. Give the second meaning its own name. Then the widening is a change to one number and a renaming of some readers, and everything built at the old width goes on being exactly itself.
There is a second thing here and it is about what a frozen thing is allowed to be ignorant of. The six-output network does not decline the seventh entry. It has no opinion about it at all: its ranking walk is six long, its scratch slice is six long, and there is nowhere in it for a seventh opinion to live. That is a stronger and cheaper property than a controller that scores seven and is refused on one of them. It costs no arithmetic, it cannot be got wrong by a weight drifting, and it is checked by one comparison in the constructor rather than by a rule that has to run every tick.
And a third, smaller, about cutting things at the founding. The wiring a lineage starts with could have kept its nerves into an output its row can never use. Nothing would have been incorrect: the check refuses the entry whatever score arrives at it. What it would have cost is twenty-four numbers in every genome of that lineage for ever, copied and crossed and mutated at every birth, to compute a score that is discarded on arrival. Founding without them and letting the world grow them back is the same discipline the sensor channels are under, and the run above measures exactly what it saved: sixty-three animals out of 1,789 carried the cost instead of all of them.
- Say which of
beast.Actsandbeast.Fixeda line should use, given only what the line does: draw a founding brain, size a card's per-entry counter, build the wiring a lineage opens with, walk the ranking inside the frozen driver. - Work out the price of a strike for a row with twice the founding bulk and half its top speed, in your head, and say whether it is more or less than the founding row's 4.8600.
- Name the four values the check can refuse an action with, and say which one of them carries no numbers in its refusal and why that is a decision.
- Explain why a founding browser has 168 genes here where it had 167, when it has exactly the same 144 links, and what those extra three numbers a birth do to a two-hundred-year run.
- Say what the seventh entry being scored 100,580 times and taken zero times tells you about where the check sits in the ranking walk.
- Given a run whose grams ledger closes perfectly and whose every figure differs from the run before it, say which kind of defect that pattern points at.
Exercise 1 — two refusals in one table. Stand the creature somewhere with no plants inside its reach and ask the check all seven again. Predict which entries are refused and with which value before you run it.
Cell 2,6 has nothing standing within a cell of it and three creatures nearby, so the mouth and the strike are refused by two different rules on the same tick:
$ go run ./cmd/seventh -mode check -at 2,6
action price ticks what the check says
rest 0.0000 1 allowed
walk 1.2150 1 allowed
sprint 4.8600 1 allowed
turn left 0.4050 1 allowed
turn right 0.4050 1 allowed
bite 0.1500 2 creature 1 at 2,6 cannot bite: no standing tissue inside reach
strike 4.8600 2 creature 1 at 2,6 cannot strike: no creature of a lower level inside reach
Two refusals, two values, and they are separate values for a reason a caller can act on: an animal that cannot bite here should move, and an animal that cannot strike here would not be helped by moving one cell. A single "not allowed" boolean would have collapsed both into the same non-answer.
Exercise 2 — put the browser one level up. Change
Trophic: 1 to Trophic: 2 in the founding row, re-run
-mode widths, and work out what changed and what did not before you look
at the numbers.
The browser column of the second block becomes the level-2 column: 168 links instead of 144, 175 MIND genes, 192 genes in the genome, 584 numbers a birth, and 56 legal pairs left open instead of 80. The founding cut now keeps the nerves into entry 6, because a row above level one could in principle have something below it.
Two things do not change, and both are instructive. The left-hand column is identical,
because a graph with six outputs has no seventh output to keep or cut. And
-mode check still refuses the strike, because raising everybody's level
by one leaves no animal below another. The wiring got bigger and the answer stayed
the same.
One pinned test fails and names the cause without you having to hunt:
go test ./internal/beast/ -run TestABrowserCanNeverStrike prints
the browser stands at level 2, and it eats plants. Put the 1 back.
Exercise 3 — settle the tie yourself. Write a test that puts two creatures below a striker inside its reach and check which one the search comes back with, first with different bulks and then with the same bulk.
Stand the striker at level 2 on a cell, put two level-1 bodies on cells touching it, tag the three of them so they carry identities in founding order, and build a view over all three:
// v is the valley the test stood up and held still
hunter := beast.Fauna[0]
hunter.Trophic = 2
a := hunter.Spawn(beast.Centre(sim.Coord{X: 5, Y: 5}), 0)
heavy := beast.Fauna[0]
heavy.Bulk = 60
one := heavy.Spawn(beast.Centre(sim.Coord{X: 5, Y: 6}), 0)
two := beast.Fauna[0].Spawn(beast.Centre(sim.Coord{X: 4, Y: 5}), 0)
herd := []*beast.Beast{a, one, two}
beast.Tag(herd, 1)
w := beast.NewView(v, herd)
q, grams := a.Quarry(w) // creature 2, 60 grams
With the bulks different, the answer is the heavier body wherever it is standing, and the order the cells are walked in has no say. Give both bodies the founding row's forty grams and the answer becomes creature 2 rather than creature 3: the tie goes to the lower identity, and identities are handed out in founding order. Swap the two spawn lines so the western body is founded first and the answer follows the identities, not the geometry. A test that asserts both of those passes in under a hundredth of a second and will keep passing on a machine that walks memory in a different order, which is the whole point of settling a tie with an integer.
The table has seven entries, the seventh is priced and refusable, and in this valley it is refused every single time, because a browser is the only thing alive and there is nothing beneath a browser. An entry that nothing can take is a rule with no world to be true in. What it needs is something standing at a level the browser is above, and the cheapest such animal to build is one that never has to catch anything: a mouth pointed at what has already stopped moving.