The Loop Closes
The score-to-action contract
The hand-written rule has been the only thing turning sensor rows into actions. The network now has the matching ends: twenty-four inputs from the senses, six outputs for the table, and 378 weights drawn from their own stream.
The six outputs are scores against the six entries of the action table, taken in table order, and the creature does the highest-scoring action the check allows. No score means anything on its own; the order of the six is the contract between arithmetic and world.
The two sides fit because their sizes were fixed earlier. The sensor row is nine rays at two numbers each, four gradient numbers and two body readings. The table has six entries. The hidden row's twelve neurons are the only chosen size in the middle.
The hard part is where the scores meet legality. A network knows nothing about water, plants or the store. The valley knows nothing about weights. This page puts the meeting at the driver: rank the scores, ask the table's check in that order, and take the first action still legal on this tick.
The Wits value
Start with the type that holds both halves. It is a Mind, so the phase can
hand it a row and take back an action without knowing what is inside it, and it carries
three things besides the network itself.
// internal/beast/wits.go
// Wits is a network wired into a creature at both ends. The sensor row
// goes in as the network's inputs, the numbers it hands back are read
// as one score for each entry of the action table, and the action taken
// is the highest-scoring one the check allows.
type Wits struct {
Net mind.Net
Self *Beast
Here *View
// Blunt is the wiring as it was first written: the top score handed
// straight to the tick with nothing asked about whether it is
// allowed. It is kept behind a flag so the failure can be run
// instead of described, and it is false in every creature this book
// ships.
Blunt bool
Runs int // forward passes this brain has actually spent
Held int // ticks it was not asked, because an action was under way
Struck int // scores the check has crossed out, added up over the run
Wanted [Acts]int // what the top score asked for, whether or not it happened
Took [Acts]int // what the creature was left holding
hid, out []float64
}
// NewWits wires one network to one creature and hands the creature back
// its own driver, so the two halves of the link are made in one place
// and cannot disagree.
func NewWits(n mind.Net, b *Beast) *Wits {
if n.In != Inputs || n.Out != Acts {
panic(fmt.Sprintf(
"a %d-%d-%d network cannot drive a creature: the sensor row is %d numbers and the action table is %d entries",
n.In, n.Hid, n.Out, Inputs, Acts))
}
w := &Wits{Net: n, Self: b}
w.hid, w.out = n.Buffers()
b.Mind = w
return w
}
Self is the creature this brain answers for. A Mind is handed
twenty-four numbers and nothing else, on purpose, and the check is a question about a body
standing somewhere and not a question about a row. The body has to arrive from
somewhere, and it arrives here, fixed once when the two are wired together and never
looked up again. Here is the world as the phase opened, and the next section
is about how it gets there. hid and out are this brain's own
scratch, made once, so no two creatures ever write into the same slice and a tick of three
thousand of them allocates nothing.
The two sizes are checked and not trusted, because getting either of them wrong is silent in the worst way. A network with twenty-three inputs reads twenty-three of the twenty-four numbers and answers confidently on the ones it read. A network with five outputs produces five scores for a six-entry table, and the entry left without a score is a thing no creature built that way will ever do in the whole history of the world. Neither mistake produces an error, a wrong answer, or a slow tick. Both produce a valley that is quietly less than it was meant to be, forever.
The bench prints both ends and the arithmetic between them, with no valley anywhere near it. Every figure on this table is a constant of the world.
$ go run ./cmd/wired -mode wire
wired: the sensor row is 24 numbers and the action table is 6 entries,
so the network between them is 24-12-6 and owns 378 weights
slot action price ticks allowed when
0 rest 0.0000 1 nothing
1 walk 1.2150 1 the cell ahead is not open water
2 sprint 4.8600 1 the cell ahead is not open water
3 left 0.4050 1 nothing
4 right 0.4050 1 nothing
5 bite 0.1500 2 tissue standing inside Reach
the row's own slots, for the ends that are not a ray:
row[ 0] ray 0 distance, 0 at the eye and 1 at the sight cap
row[ 1] ray 0 code, what that ray is worth to a browser
row[18] the moisture slope, going east
row[19] the moisture slope, going south
row[20] the standing-tissue slope, going east
row[21] the standing-tissue slope, going south
row[22] the store, as a share of full
row[23] the speed, as a share of top
one creature deciding once: 360 multiplications, 378 additions, 12 tanh,
then at most 6 calls to the check and no allocation at all
Slot 0 is rest and slot 5 is a bite, and the table is indexed by exactly those numbers.
That is the whole of the output end of the wiring: output k of the network is
Table[k], no lookup, no map, no names. It holds because the constant order in
act.go was written down once and nothing else in this package is permitted to
decide what a creature can do. If a seventh entry ever joins the table, every network in
the valley becomes wrong on the same day, which is exactly the property you want from a
number that load-bearing: the mistake is loud and total instead of quiet and partial.
Three hundred and sixty multiplications is what one creature thinks with. Set that beside the four hundred-odd samples a fan of nine rays walks, because the two costs are the same order and only one of them is the part people expect to be expensive.
The ranked legality check
There are three places the join could go, and they are not equally good.
The first is inside the network. Teach it what is legal by putting legality into the sensor row, or add a seventh output that means "and this one is allowed". Both are out. The row is twenty-four numbers and its order is canon for every controller in this volume, so it cannot grow a legality column without invalidating every brain ever drawn. And a network told in advance which options are open has been handed the answer to the only question anybody wanted it to work out.
The second is after the pick, in the tick. Take the highest score, hand it to
Do, and let the check refuse it. This costs nothing to build, because
Do already does exactly that: a refused action becomes a rest, and the
refusal comes back to the caller as an error. Its price is that the network's second
choice is thrown away unread. A creature standing at the pond's edge whose best number is
"walk" rests, and rests, and rests, while five other numbers it produced sit there saying
what it would rather do instead.
The third is between them, and it is the one this chapter builds. Six numbers put the whole table in an order, and an order carries more than a single answer does. Walk it from the top, put each entry to the check in turn, and the first one that comes back allowed is what happens. Nothing is thrown away that did not have to be, and the tick never has to refuse anything, because nothing illegal ever reaches it.
Which settles what the code does and reopens where it lives. Not in Do, which
is handed one action and has no idea there were five others. Not in Pick
either, at first glance, because Pick sees a row of numbers and the check
needs a valley. So the ranking walk goes into the one type that can hold both: the brain,
which already knows its creature, given a pointer to the world the phase is reading.
func (w *Wits) Pick(row *Senses) Act {
b := w.Self
if b.Owed > 0 {
w.Held++
return b.Busy
}
w.Net.Forward(row[:], w.hid, w.out)
w.Runs++
var struck [Acts]bool
for n := range Acts {
best := 0
for i := range Acts {
if struck[i] {
continue
}
if struck[best] || w.out[i] > w.out[best] {
best = i
}
}
if n == 0 {
w.Wanted[best]++
}
struck[best] = true
a := Act(best)
if w.Blunt || w.Here == nil || b.Legal(a, w.Here) == nil {
w.Took[best]++
w.Struck += n
return a
}
}
w.Took[Rest]++
return Rest
}
The first four lines are the note the action chapter left. A creature part-way through an
action it took earlier is not asked anything: the tick already belongs to that action, and
Do would drop whatever came back. The forward pass is the most expensive
thing a creature does, and spending one to produce a number nobody will read is waste in
its plainest form. A bite occupies two ticks, so in a valley of biters this branch skips
about a quarter of all creature-ticks, which the census below counts.
The ranking is walked without sorting and without allocating. Six entries make the naive
version cheap: find the largest score not yet crossed out, try it, cross it out, repeat.
Thirty-six comparisons in the worst case against a slice header and a sort, and
struck is an array of six bools on the stack. The comparison is strict, so
two scores equal to the last bit are settled by table order, and the same tie comes out
the same way on every machine that runs it. That is a fact the digests depend on, not a
matter of taste.
The loop always ends before it runs out of entries. Rest is priced at nothing, so the store gate cannot refuse it, and its permission test is the one that says yes to everything. A creature that may do nothing else may always do nothing, and the line after the loop has never executed.
Here still has to arrive. The phase opens by building one index of the valley
by cell, so that nine rays from three thousand creatures do not walk the stand list every
time; the same index answers everything the check wants to know. Handing it over is one
function and one type assertion.
// Watch hands one reading of the world to every network-driven creature
// on a list. It is the whole of the join: the phase gives a creature's
// eyes the world it opened on, and this gives the same world to the
// creature's judgement.
func Watch(herd []*Beast, v *View) {
for _, b := range herd {
if w, ok := b.Mind.(*Wits); ok {
w.Here = v
}
}
}
// cmd/wired: wiring a roster into the valley's tick, with the join in it.
func hook(v *terra.Valley, r *beast.Roster, buried func(beast.Carcass), no func(error)) {
phase := r.Hook(v, scale(v), buried, no)
v.Phase = func() {
beast.Watch(r.Live, beast.NewView(v, r.Live))
phase()
}
}
Anything on the roster driven by something else is left alone, so a valley holding
hand-written rules and networks side by side needs one call and no branches. And a brain
that has never been handed a view still works: Pick checks for the nil,
takes the top score, and lets the tick refuse it. The wiring degrades to the second
design instead of crashing, which is the correct behaviour for a seam somebody might use
without reading this page.
There is a cost in those two lines and it should be said out loud. The index is built
twice a tick: once here, and once inside the phase, over the same valley and the same
roster. The two are identical and one of them is waste. It is the price of a seam that
lets a Mind see its inputs and nothing else, and in a valley of nineteen
creatures it is invisible. In a valley of three thousand it is a line on a profile with
somebody's name on it.
Now put a creature somewhere awkward and open up a single tick. Cell 3,4 is on the western shore of the pond, facing east, with water in front of it and nothing standing anywhere near its mouth.
$ go run ./cmd/wired -mode score -at 3,4
wired: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams
creature 1 on 3,4 facing 0 degrees, 400.0000 in the store,
brain 0 off stream 14, 24-12-6, tanh through the middle row
19 other creatures standing about on stream 12, which its rays can see
the row it read, the entries that are not zero:
row[ 0] 0.0625 ray 0, how far it went
row[ 1] -0.5000 ray 0, what it met
row[ 2] 0.0625 ray 1, how far it went
row[ 3] -0.5000 ray 1, what it met
row[ 4] 0.0625 ray 2, how far it went
row[ 5] -0.5000 ray 2, what it met
row[ 6] 0.0625 ray 3, how far it went
row[ 7] -0.5000 ray 3, what it met
row[ 8] 0.0417 ray 4, how far it went
row[ 9] -0.5000 ray 4, what it met
row[10] 0.0625 ray 5, how far it went
row[11] -0.5000 ray 5, what it met
row[12] 0.0625 ray 6, how far it went
row[13] -0.5000 ray 6, what it met
row[14] 0.0625 ray 7, how far it went
row[15] -0.5000 ray 7, what it met
row[16] 0.0625 ray 8, how far it went
row[17] 0.2500 ray 8, what it met
row[19] -0.0341 the moisture slope, going south
row[22] 1.0000 the store, as a share of full
the six numbers that came back, in table order:
rest walk sprint left right bite
score -0.1918 0.4753 -0.0323 -0.0721 -0.0325 0.4652
rank action score check why not
1 walk 0.4753 refused creature 1 at 4,4 cannot walk: the cell ahead is open water or off the grid
2 bite 0.4652 refused creature 1 at 3,4 cannot bite: no standing tissue inside reach
3 sprint -0.0323 refused creature 1 at 4,4 cannot sprint: the cell ahead is open water or off the grid
4 right -0.0325 allowed
5 left -0.0721 allowed
6 rest -0.1918 allowed
the creature does turn right: 3 scores crossed out above it
Eight of the nine rays come back at minus a half, which is the code for water, and none of the eight got further than a sixteenth of the sight range: the animal is looking straight into the pond from a few pixels off the edge. Ray 8, the last one round to the right, met one of the other eighteen creatures instead. Of the four gradient slots only one prints at all, at about a thirtieth, so the ground here holds close to the same water in every direction and the same standing tissue in every direction, that amount being none. The store is full, so slot 22 reads exactly one.
Then six numbers, and three of them are refused. Walk and sprint would put weight on 4,4, which is open water. The bite has nothing inside a reach of one cell. The creature turns right, on the fourth-ranked score of six, two ten-thousandths below the sprint it was not allowed to take. Under the second design it would have rested. Under this one it turns, which at least changes what its rays will see next tick.
Write the six down in table order, from the run above: rest at -0.1918, walk at 0.4753, sprint at -0.0323, turn left at -0.0721, turn right at -0.0325, and bite at 0.4652. Two questions get asked of that list, and both of them are comparisons.
The first is the plain one: which is biggest? Walk, at 0.4753, beating the bite by 0.0101. The second is the one the check forces: which is biggest among the ones allowed here? The allowed list on this tick is turn left, turn right and rest, and the biggest of those three is turn right at -0.0325.
Those two questions have different answers, and the difference is the whole design. Taking the biggest of six and then testing it gives you walk, which cannot happen, so the tick substitutes a rest and the creature does an action that scored last. Taking the biggest of the allowed three gives you turn right, which scored fourth. Fourth is worse than first and enormously better than sixth.
Now add ten to every one of the six. They become 9.8082, 10.4753, 9.9677, 9.9279, 9.9675 and 10.4652. Every gap between every pair is the number it was before, so both comparisons come out exactly where they came out before, and the creature does the same thing. That is the property the output layer was left bare for. A squash on the output row would spend twelve multiplications and a transcendental function per creature per tick buying a range nothing reads.
In those terms: adding c to every s[a] leaves the biggest of L exactly where it was, for any L; and the biggest of A is not the biggest of L unless the biggest of A happens to be in L. Both are dull sentences about lists of numbers, and both are load-bearing.
The second claim is arithmetic, so it does not need testing. The first one runs through a real network and a real check, so it can be. Adding ten to all six output biases adds exactly ten to all six scores, and the bench does it and asks the same tick again.
$ go run ./cmd/wired -mode score -at 3,4 -lift 10 | tail -12
rest walk sprint left right bite
score 9.8082 10.4753 9.9677 9.9279 9.9675 10.4652
rank action score check why not
1 walk 10.4753 refused creature 1 at 4,4 cannot walk: the cell ahead is open water or off the grid
2 bite 10.4652 refused creature 1 at 3,4 cannot bite: no standing tissue inside reach
3 sprint 9.9677 refused creature 1 at 4,4 cannot sprint: the cell ahead is open water or off the grid
4 right 9.9675 allowed
5 left 9.9279 allowed
6 rest 9.8082 allowed
the creature does turn right: 3 scores crossed out above it
Every score up by ten to the last printed digit, the ranking unmoved, the same action taken. Six numbers with an arbitrary offset in them still name one action, and that is the only thing being asked of them.
Twenty ticks of an untrained network
One tick proves the wiring holds. Twenty of them start to show what an untrained network is actually like to live next to. The bench stands the same creature on the same pond shore, holds the valley still so nothing grows back underneath it, and prints what the top score asked for beside what the creature was left holding.
$ go run ./cmd/wired -mode tape -at 3,4 -ticks 20
wired: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams
creature 1 put on 3,4 facing 0 degrees, 400.0000 in the store
driven by brain 0 off stream 14
the valley is held still, so nothing grows back while the creature eats
tick cell facing wanted did price haul grams store
1 3,4 30.0 bite turn right 0.4050 0.0000 0.0000 399.4350
2 3,4 60.0 bite turn right 0.4050 0.0000 0.0000 398.8700
3 3,4 90.0 bite turn right 0.4050 0.0000 0.0000 398.3050
4 3,4 90.0 bite walk 0.0000 0.3037 0.0000 397.8413
5 3,4 90.0 bite walk 0.0000 1.2150 0.0000 396.4663
6 3,5 90.0 bite walk 0.0000 1.2150 0.0000 395.0913
7 3,5 90.0 bite walk 0.0000 1.2150 0.0000 393.7163
8 3,5 90.0 bite walk 0.0000 1.2150 0.0000 392.3413
9 3,5 90.0 bite walk 0.0000 1.2150 0.0000 390.9663
10 3,5 90.0 bite walk 0.0000 1.2150 0.0000 389.5913
11 3,6 90.0 bite walk 0.0000 1.2150 0.0000 388.2163
12 3,6 90.0 bite walk 0.0000 1.2150 0.0000 386.8413
13 3,6 90.0 bite walk 0.0000 1.2150 0.0000 385.4663
14 3,6 90.0 bite walk 0.0000 1.2150 0.0000 384.0913
15 3,7 90.0 bite walk 0.0000 1.2150 0.0000 382.7163
16 3,7 120.0 bite turn right 0.4050 0.3037 0.0000 381.8475
17 3,7 150.0 bite turn right 0.4050 0.0000 0.0000 381.2825
18 3,7 150.0 bite walk 0.0000 0.3037 0.0000 380.8188
19 3,7 150.0 bite walk 0.0000 1.2150 0.0000 379.4438
20 3,7 150.0 bite walk 0.0000 1.2150 0.0000 378.0688
20 ticks: 15 walk, 5 turn right
0.0000 grams eaten, 378.0688 left in the store, standing on 3,7 facing 150.0
20 forward passes spent, 0 ticks held by an action already under way
the check crossed out 25 scores over the run, and the tick refused 0
The wanted column says bite on every one of the twenty ticks. That is not a bug and it is not the network noticing food, since there is none within sight. It is a network whose weights were drawn once and never adjusted, being fed rows that barely move: the bite output happens to sit highest for almost anything this creature can see, so it asks for a bite while walking past a pond with nothing in its mouth's range at any point.
What the creature actually does is the check's answer, tick by tick. It turns right three
times until it is facing south, walks the length of the pond's western shore, turns twice
more at the bottom, and carries on. Twenty-five scores crossed out over twenty ticks: an
average of better than one refusal per decision, all of them absorbed inside
Pick. The last line is the one to hold onto. The tick refused nothing at all,
because nothing illegal ever got as far as the tick.
The first version of this wiring did not walk a ranking. It took the largest of the six,
handed it to Do, and let the existing check refuse it, on the reasoning
that the refusal machinery was already built and already tested and there was no sense
writing a second one. That version is still in the type behind a Blunt
flag, so the failure can be run instead of described.
$ go run ./cmd/wired -mode tape -at 3,4 -ticks 20 -blunt | tail -12
14 3,4 0.0 bite rest 0.0000 0.0000 0.0000 397.7600
15 3,4 0.0 bite rest 0.0000 0.0000 0.0000 397.6000
16 3,4 0.0 bite rest 0.0000 0.0000 0.0000 397.4400
17 3,4 0.0 bite rest 0.0000 0.0000 0.0000 397.2800
18 3,4 0.0 bite rest 0.0000 0.0000 0.0000 397.1200
19 3,4 0.0 bite rest 0.0000 0.0000 0.0000 396.9600
20 3,4 0.0 bite rest 0.0000 0.0000 0.0000 396.8000
20 ticks: 20 rest
0.0000 grams eaten, 396.8000 left in the store, standing on 3,4 facing 0.0
20 forward passes spent, 0 ticks held by an action already under way
the check crossed out 0 scores over the run, and the tick refused 20
Twenty ticks, twenty rests, and the creature has not moved a pixel or turned a degree.
The symptom looks like a dead network: same action forever, no response to anything.
It is not, and the tape says so in the column beside it, because the wanted column is
not constant for a network that is doing nothing. It is constant because the top score
is a bite on every tick, and there is nothing to bite on this cell, so the check refuses
it on every tick and Do writes a rest in its place. The network is
producing six different numbers, five of them naming things the creature could actually
have done, and the wiring is throwing all five away.
The reasoning from symptom to cause runs through the last line. The tick refused twenty of twenty. A refusal is not free information: it says the driver asked for something the world would not carry, and a driver that asks for the same impossible thing twenty times running is a driver whose second opinion nobody is collecting. Compare that line with the same run above, where the check crossed out twenty-five scores and the tick refused none, and the two designs separate cleanly. Both consult the check exactly as often. Only one of them does anything with the answer.
Nineteen brains in one valley
Now let a population loose. Nineteen creatures scattered over the valley on stream 12, each drawn its own three hundred and seventy-eight weights off stream 14 in roster order, each with a full store, the whole roster registered into the valley's tick, and two years of seasons run over the top of it. Nothing is trained, nothing is selected, nothing reproduces. This is what a drawn brain does with a body.
$ go run ./cmd/wired -mode herd -herd 24 -years 2
wired: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams
19 creatures on stream 12, 400.0000 in every store, each driven by
a brain of its own off stream 14, never trained
the creature phase runs once a tick out of terra.Valley.Phase
tick season alive eaten carcass standing litter
1351 summer 15 211.0096 160.0000 278.02 89.0844
2251 autumn 10 722.3810 360.0000 453.04 7.6326
3151 winter 4 1019.1334 600.0000 101.60 168.6833
4051 spring 2 1188.1334 680.0000 1522.51 48.8258
4951 summer 2 1358.3834 680.0000 616.52 0.0372
5851 autumn 2 1526.6334 680.0000 1466.18 30.4865
6751 winter 2 1751.6334 680.0000 1064.60 5.9419
7651 spring 2 1976.1334 680.0000 2554.88 1.2100
19 creatures started, 17 struck off, 2 still walking after 6750 ticks
wanted handed on share
rest 8175 10756 42.0%
walk 2511 927 3.6%
sprint 2478 580 2.3%
left 1523 2280 8.9%
right 1818 3091 12.1%
bite 9101 7972 31.1%
wanted is the top score, handed on is what came back out of Pick
25606 forward passes spent, 7971 ticks held by an action under way
the check crossed out 6654 scores inside Pick over the run
where they stopped, 17 of them:
on open soil 10
on the rocky rim 7
first at tick 1044 on 9,5, last at tick 3400 on 11,5
still walking, and what each of them lived on:
id cell grams eaten store cells a tick grams in reach
1 3,0 824.5932 399.6900 0.0000 109.4227
18 6,1 535.1945 399.5300 0.0000 69.0776
the books in grams
in 46567.944597 founded, grown and stood up
out 46567.944597 standing, walking, flying, lying, spent
difference -0.000000 -1.455e-11, which is the last bits of the adding
17 creatures struck off at 40 grams a body is 680.000000 grams of carcass
handed to terra.Bed.Fall 680.000000 grams
Seventeen of nineteen are dead inside two years, and four of them are gone by the first census line, four hundred and fifty ticks in. That is the result, and the temptation is to read it as a broken network and go looking for the bug. It is not one, and the way to be sure is to ask what a correct wiring of an untrained network would look like if you saw it. Untrained means the weights are three hundred and seventy-eight numbers drawn out of a stream, arranged in no particular relation to anything about being alive. A creature driven by them should do something consistent, arbitrary, and unhelpful. It should not do something random, because the same row through the same weights gives the same answer twice; and it should not do something useful, because nothing has ever pushed those weights toward useful. Consistent and stupid is the correct output, and consistent and stupid is what the tape and the census both show.
The census is the clearest evidence the loop is closed and not idling. Look at wanted
against handed on. The top score asked for a bite 9,101 times and a bite came out of
Pick 7,972 times, so the two ends of that row are within an eighth of each
other. Rest was wanted 8,175 times and handed on 10,756, picking up the surplus as the
fallback at the bottom of every exhausted ranking. Walk was wanted 2,511 times and came out
927, which is what a valley with a pond through the middle of it and a rocky edge does to a
creature that wants to go forwards. Six thousand six hundred and fifty-four scores were
crossed out over twenty-five thousand decisions, a little over one struck score per four
decisions, and every one of them is a creature whose second or third opinion got used
instead of dropped.
Nobody drowned. Not one of the seventeen died in the water, because a step into open water is one of the three things the check refuses outright, and a creature whose brain wants to walk into the pond is turned away from it every tick until something else scores higher. Ten died on open soil and seven on the rocky rim, which is the valley's real trap for an animal with no reason to prefer one kind of ground to another: nothing has ever grown on the rim and nothing ever will, so a creature that wanders onto it is walking on a surface where its store can only go down.
The two survivors need close reading, because they are not a success. Both are standing exactly still at zero cells a tick. Both have practically full stores. Both have a large plant inside a reach of one cell, and between them they have eaten 1,360 grams of the 1,976 the whole population took off the valley. They are creatures whose drawn weights happen to put the bite output on top for almost every row, which parked them next to the first plant they touched and kept them there for six thousand ticks. The same disposition that killed the others by making them ask for a bite on bare soil kept these two alive by making them ask for a bite next to a tree. Nothing about them is skill.
For a control, drive the same nineteen creatures with the hand-written rule, in the same valley, from the same seed.
$ go run ./cmd/wired -mode herd -herd 24 -years 2 -mind rule | tail -28
tick season alive eaten carcass standing litter
1351 summer 19 909.8334 0.0000 107.67 0.0897
2251 autumn 19 2505.1229 0.0000 112.94 4.8714
3151 winter 10 2734.2830 360.0000 0.00 353.2494
4051 spring 0 2734.2830 760.0000 826.99 146.2922
4951 summer 0 2734.2830 760.0000 546.88 0.1264
5851 autumn 0 2734.2830 760.0000 1128.00 22.0877
6751 winter 0 2734.2830 760.0000 982.00 4.3049
7651 spring 0 2734.2830 760.0000 2029.63 0.8348
19 creatures started, 19 struck off, 0 still walking after 6750 ticks
the tick refused an action 9243 times:
no standing tissue inside reach 5562
the cell ahead is open water or off the grid 3639
the store cannot pay for it 42
where they stopped, 19 of them:
on open soil 14
on the rocky rim 5
first at tick 2800 on 5,7, last at tick 3293 on 0,3
the books in grams
in 34554.772631 founded, grown and stood up
out 34554.772631 standing, walking, flying, lying, spent
difference -0.000000 -1.091e-10, which is the last bits of the adding
19 creatures struck off at 40 grams a body is 760.000000 grams of carcass
handed to terra.Bed.Fall 760.000000 grams
The rule is a far better forager and it kills everything. All nineteen survive the first year and eat 2,734 grams, against 1,976 for the drawn brains over twice as long, and by the winter of the second year there is nothing standing in the valley at all. Then every one of them starves inside five hundred ticks of each other, the first on tick 2,800 and the last on 3,293. Nineteen animals that all want the nearest plant, all the time, strip a twelve by eight valley faster than it grows, and the rule has no line in it about stopping.
Do not take that as the networks winning. They lost seventeen of nineteen and left the valley standing mostly because they were too incompetent to eat it. What the pair of runs shows is a different thing, and a better one: the machinery underneath both is identical. Same table, same check, same phase, and both sets of books close on their own totals to the last bits of the adding. Only the object behind one interface changed, and the valley did something completely different. That is the seam doing its job.
Why the loop is closed
Two mechanisms in this chapter outlive the creatures they were built for, and they solve different problems.
The first is reading a network's outputs as an ordering over a fixed table instead of as values. This is what lets a completely general lump of arithmetic drive a completely specific machine. The network never learns what a sprint is; it produces a number in slot 2, and slot 2 is a sprint because a constant in another file says so. Nothing anywhere converts between the two domains, because there is nothing to convert: an index is an index. And because only the ordering is read, every question about the size of the outputs disappears. They can be any range at all.
The second is that a policy which produces a ranking survives a world that vetoes, and a policy which produces a single answer does not. The check knows three things the network cannot: what the ground is like ahead, whether there is anything within reach, and what the store can pay for. Those are facts about a place, and the row deliberately carries none of them directly. Given a ranking, the veto costs the creature its first choice. Given one answer, the veto costs it the tick. That difference is not a matter of degree, and the twenty-rest tape in the failure box is what it looks like at full strength.
The last thing this chapter owes is a look at the valley. Everything above is a table of numbers about animals nobody can see, and the client has been able to draw this ground since the terrarium volume: water, rim, soil, and every plant grown out of its own grammar. A creature is one blit on top of that.
// The two frames of the walker on the sheet the client has been loading
// since the sprite chapter, and the shadow that goes under both.
const (
Stand = 0
Step = 1
Shadow = 2
)
// blit puts one creature into the frame: the shadow first, then the
// walker, standing when its legs are still and mid-stride when they are
// not. The sprite is 16 pixels square and a cell is 16 pixels wide, so
// the top-left corner is the creature's own position less half a cell
// in each direction.
func blit(b *render.Buffer, sh *render.Sheet, c *beast.Beast) {
x := int(math.Round(c.Pos.X)) - terra.Tile/2
y := int(math.Round(c.Pos.Y)) - terra.Tile/2
b.Blit(sh, sh.Frame(Shadow, 0), x, y)
frame := Stand
if c.Speed() > 0.01 {
frame = Step
}
b.Blit(sh, sh.Frame(frame, 0), x, y)
}
Nothing in there asks what a creature is thinking. It asks where the body is, in world pixels, and how fast it is going, and both of those are numbers the movement chapters already keep. The shadow goes down first so the walker's transparent pixels let it through, which is the alpha mixing the sprite chapter built and nothing more. Which of the two frames gets picked is the only place the drawing and the controller touch at all, and even that is only through a speed.
$ go run ./cmd/wired -mode draw -herd 24 -ticks 120 -shot assets/frames/valley-nineteen-brains.png
wired: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams
120 ticks on from midsummer, 19 creatures still walking of the 19 founded
12 plants standing at 1303.3 grams, 44.0000 grams eaten, 0.0000 in the litter
4 of the walkers had their legs moving when the frame was taken
the valley 192 by 128 pixels, 191830241279e65d391e6a6d78871bed59676d8e5013006192da0f39a658dbd0
wrote assets/frames/valley-nineteen-brains.png at 3 times, 576 by 384
assets/frames/valley-nineteen-brains.png: a hundred and twenty ticks after
the population was founded, every creature drawn with the same two calls. Four of the
nineteen were moving when the frame was taken. Several are already out on the rim, where
nothing grows, and none of them are in the pond, because the ground rule refuses that
step whatever a brain scores it.
One hundred and twenty ticks in, the frame reads as a valley with animals in it, which is about as far as an honest description goes. Forty-four grams have moved from plants into bodies. Nothing has died yet. The creatures are still spread roughly where stream 12 scattered them, and several of them are standing on rock, which the two-year census says is where seven of the seventeen deaths happen. A picture of a valley of untrained creatures looks exactly like a picture of a valley of trained ones, and that is the last reason to trust the census over the frame.
- Handed the six scores from a tick and the check's answer for each, say which action happens, and say how that differs from taking the best of the six and testing it afterwards.
- Explain why adding the same number to all six outputs changes nothing, and use that to say what a squash on the output row would and would not buy.
- Name the two sizes
NewWitsrefuses to guess at, and describe what a network with five outputs would do to a valley without ever producing an error. - Say why a creature with
Owedabove zero is not put through a forward pass, and roughly what share of ticks that skips in a valley of biters. - Shown a tape where the wanted column reads the same action twenty times running, tell an untrained network apart from a broken one using the column beside it.
- Say why the same view has to reach both a creature's rays and its check, and
what happens to a
Witsthat was never handed one.
Exercise 1 — a different set of weights on the same tick.
The creature on 3,4 was driven by the first brain off the stream. Ask the eighth with
go run ./cmd/wired -mode score -at 3,4 -brain 7 and predict how much of
the answer moves before you look.
Everything about the row is identical, because the row is a fact about the valley and not about the brain. Everything about the six numbers is different, because nothing links one draw off the stream to the next. The ranking that comes out is a different order, only one score is crossed out instead of three, and the creature turns left rather than right. The two brains agree on nothing except that walking east scores highest, and the ground refuses that for both of them.
$ go run ./cmd/wired -mode score -at 3,4 -brain 7 | tail -11
score -0.1536 0.0539 -0.1272 0.0443 -0.0136 -0.3473
rank action score check why not
1 walk 0.0539 refused creature 1 at 4,4 cannot walk: the cell ahead is open water or off the grid
2 left 0.0443 allowed
3 right -0.0136 allowed
4 sprint -0.1272 refused creature 1 at 4,4 cannot sprint: the cell ahead is open water or off the grid
5 rest -0.1536 allowed
6 bite -0.3473 refused creature 1 at 3,4 cannot bite: no standing tissue inside reach
the creature does turn left: 1 score crossed out above it
Exercise 2 — put it next to dinner. Cell 3,1 has the
biggest plant in the valley beside it. Run the same brain there with
go run ./cmd/wired -mode tape -at 3,1 -ticks 20 and see whether an animal
that wants to bite everything manages to eat.
It eats nothing at all. Twenty walks in a straight line east, four cells crossed, 26.6 units of store gone, of which 3.2 is the standing body and the rest is the legs, and not one gram taken. The top score on 3,1 is a walk and not a bite, and the ranking never gets past its first entry, because east of 3,1 is open soil all the way. The creature that wanted to bite the world on the pond shore walks away from the one plant it could actually have reached. Position, not disposition, decided both runs.
$ go run ./cmd/wired -mode tape -at 3,1 -ticks 20 | tail -8
18 7,1 0.0 walk walk 0.0000 1.2150 0.0000 376.1612
19 7,1 0.0 walk walk 0.0000 1.2150 0.0000 374.7862
20 7,1 0.0 walk walk 0.0000 1.2150 0.0000 373.4112
20 ticks: 20 walk
0.0000 grams eaten, 373.4112 left in the store, standing on 7,1 facing 0.0
20 forward passes spent, 0 ticks held by an action already under way
the check crossed out 0 scores over the run, and the tick refused 0
Exercise 3 — run the whole valley on the blunt wiring.
The failure box showed one creature for twenty ticks. Run nineteen for two years with
go run ./cmd/wired -mode herd -herd 24 -years 2 -blunt and decide from
the numbers whether the ranking is a survival improvement.
It is not, and saying so is more useful than pretending otherwise. Three survive instead of two, and the population eats 1,664 grams instead of 1,976. What changes by an enormous margin is the refusal count: 29,388 against nothing at all, more than one refused action for every two forward passes, twenty-one thousand of them a step into water or over the rim. The ranking is a correctness improvement rather than a fitness one. It makes the creature's action be the network's own choice among what was possible, instead of having that choice silently replaced by a rest twenty-nine thousand times.
$ go run ./cmd/wired -mode herd -herd 24 -years 2 -blunt | tail -25
46399 forward passes spent, 6665 ticks held by an action under way
the check crossed out 0 scores inside Pick over the run
the tick refused an action 29388 times:
no standing tissue inside reach 7860
the cell ahead is open water or off the grid 21412
the store cannot pay for it 116
where they stopped, 16 of them:
on open soil 9
on the rocky rim 7
first at tick 1605 on 10,5, last at tick 3400 on 11,5
still walking, and what each of them lived on:
id cell grams eaten store cells a tick grams in reach
1 10,2 505.9203 351.7900 0.0000 56.6813
3 10,2 505.6771 351.7900 0.0000 56.6813
18 7,1 652.0000 399.5300 0.0000 165.9607
the books in grams
in 44373.483451 founded, grown and stood up
out 44373.483451 standing, walking, flying, lying, spent
difference 0.000000 0.000e+00, which is the last bits of the adding
16 creatures struck off at 40 grams a body is 640.000000 grams of carcass
handed to terra.Bed.Fall 640.000000 grams
A creature can now see the valley, think about what it saw, do the best thing it is allowed to do about it, and be drawn doing so. Nineteen of them ran for two years inside a tick that also grew plants, moved weather and rotted litter, and every gram was still accounted for at the end of it. What nothing here has measured is the price. Every one of those decisions was 360 multiplications and up to six calls to a check, on top of four hundred ray samples, and the tick has a hundred milliseconds in it of which the creatures were promised half. Nineteen animals will not tell you whether that promise can be kept.