What the Body Leaves
The death line in the roster
A creature can see, choose and pay, and every tape still ends with the animal standing there. Even a store that has been below zero for two hundred ticks keeps being charged.
A creature whose store reaches nothing when its tick closes is struck off the roster at the end of the phase, never in the middle of it, and every gram of its body goes onto the cell it was standing on as litter. The pile rots through the same bed call the plants have used since winter entered the valley.
The timing matters. Clamping the store at zero forgives a bill, so the arithmetic leaves the store under zero and marks the body dead. Removing the body in the middle of the phase would change which creature gets the next turn and which index the loop reads next.
The grams matter too. A browser is forty grams of body. If it vanishes, forty grams leave the world without a line in the books. If it stays in the roster after death, the valley is holding tissue that should be rotting.
The roster and the valley tick are the two missing pieces. The roster owns identities, order and running totals; the tick gives the carcass a place in the same schedule as plant litter. The nutrient books then expose the second half of the design: a creature has grams to return, but no mineral account of its own.
The roster list and tick hook
Start with the list, because everything else hangs off it. It holds the creatures in the order they are stepped, hands out the identities the log names them by, and keeps the running totals that a dead creature would otherwise take with it when it goes.
// internal/beast/roster.go
// Roster is every creature the valley is running, held in one place
// and stepped in one order. Live is that list and its order is the
// stepping order: creatures are added in founding order and never
// re-sorted, so the sequence in which three animals reach the same
// plant is a fact about the run.
//
// The five totals are the roster's half of the valley's books: three
// in grams and two counts. They are kept here and not on the creatures
// because a dead creature leaves the list, and a number that leaves
// with it is a number that stops being in anybody's books.
type Roster struct {
Live []*Beast // the creatures, in the order they are stepped
Next sim.EntityID // the next identity nobody has ever carried
Stood float64 // grams of body every creature ever put on this roster was stood up with
Ate float64 // grams this roster has taken off standing plants
Fell float64 // grams of carcass this roster has handed to the litter
Gone int // creatures struck off
Steps int // creature-ticks this roster has actually handed out
eyes []Ray // one fan, read into once per creature per tick
row Senses // one sensor row, filled once per creature per tick
}
// NewRoster opens an empty one. Identities start at 1, because 0 is the
// value a creature carries before anything has named it and Tag reads
// it as unclaimed.
func NewRoster() *Roster {
return &Roster{Next: 1, eyes: Eyes()}
}
// Add puts one creature on the roster, hands it the next identity, and
// charges its body to the opening stock. Those grams were never
// manufactured out of light by anything in this valley: a founding
// creature is opening stock exactly as a founding plant is, and the
// books have to say so before the run starts rather than discover it
// afterwards.
func (r *Roster) Add(b *Beast) {
if b.ID == 0 {
b.ID = r.Next
r.Next++
}
r.Stood += b.Kind.Bulk
r.Live = append(r.Live, b)
}
// Walking is the grams standing in living creature bodies right now. A
// browser's body does not change mass: what a mouthful buys is energy
// in the store, and the store is not made of grams. So this is the
// count times the row's bulk, and it goes down in whole bodies.
func (r *Roster) Walking() float64 {
t := 0.0
for _, b := range r.Live {
t += b.Kind.Bulk
}
return t
}
Ate and Fell sit on the roster and not on the creature, and
that placement is the whole reason the roster keeps books at all. Every creature already
counts the grams it has taken off the valley. The moment it is struck off the list, that
count goes out of scope with it, and the valley's total silently drops by whatever the
dead animal had eaten in its life. A total that has to survive the thing it is counting
belongs to whatever outlives it.
Stood is the same argument pointed the other way. A creature is put in the
valley by a bench with a body it did not grow, exactly as a founding plant is stood up
holding tissue no leaf ever fixed. The terrarium already has a word for that,
Opened, and it counts it as stock the valley began with. A founding
population of creatures is more of the same, so it is counted the same, on the tick they
arrive, long before anybody notices the sums are out.
Now the tick. The creature phase has to run at one particular point in it, and the point is not a matter of taste. A creature grazes tissue, so it has to come after the plants have grown this tick's grams, or a mouth is always eating yesterday. A carcass lands in the litter, so it has to come before the weather works on the ground, or a body that died this morning sits untouched until tomorrow while a plant that died beside it is already rotting. There is exactly one gap that satisfies both, and terra gets one new field to mark it.
// internal/terra/valley.go, inside type Valley
// Phase is the one seam this valley leaves for whatever lives in
// it. It is called once a tick, after the plants have eaten and
// before the weather works on the ground, and terra neither knows
// nor cares what registers itself here. A valley with nothing
// walking about in it leaves it nil, and a nil hook is not a
// branch anything can see from outside: the run it produces is the
// run the terrarium produced before this field existed.
Phase func()
// internal/terra/valley.go, inside func (v *Valley) Tick
v.Stands = live
// Whatever lives in this valley gets the tick here: after every
// plant has eaten, grown, seeded and been buried, and before the
// rain, the sun and the rotting touch the ground. A creature that
// grazes on this line takes tissue that grew a few statements ago,
// and anything it drops on the ground is lying there in time for
// the rotting at the bottom of this same tick.
if v.Phase != nil {
v.Phase()
}
v.Bed.Shower(v.Fall*wet, v.Full)
// internal/beast/roster.go, continued
// Hook is the roster wrapped up as the one function terra.Valley.Phase
// takes: a closure over this roster and this valley, holding the scale
// the sensor row is divided by. Registering it is the whole of wiring
// creatures into the tick.
//
// Deaths and refusals go to the caller through the two functions,
// because terra's hook hands nothing back and this package has no log
// of its own. Either may be nil in a run that does not want them.
func (r *Roster) Hook(v *terra.Valley, sc Scale, buried func(Carcass), no func(error)) func() {
return func() {
died, refused := r.Phase(v, sc)
if buried != nil {
for _, c := range died {
buried(c)
}
}
if no != nil {
for _, err := range refused {
no(err)
}
}
}
}
A function field and a nil check is the smallest thing that could work here, and small
matters more than usual, because terra is the package this whole world's
arithmetic has been settled in. It learns nothing. It does not import
beast, has no idea a creature exists, and cannot tell whether the thing it
just called ate a plant or printed a line. Everything specific lives in the closure the
other package builds, which is why the hook takes no arguments and returns nothing:
anything either side needs, the closure already has.
The nil check is the other half. A valley with nobody in it takes a branch that does nothing, and doing nothing changes no float in the world, so a terrarium run with this field present has to be bit for bit the terrarium run without it. That is a claim, and it is cheap to check.
$ go run ./cmd/books -mode quiet -years 12 -herd 4 -store 400
books: seed 5, 12 years, the valley the terrarium closed on
brake 1, creep 0.0010, rim walled and cracked
world census
terra.Valley.Phase is nil 54a0214c09b3d357 bfb496d523badb49
an empty roster registered into it 54a0214c09b3d357 bfb496d523badb49
4 creatures on that roster 9814edfd97afddc3 c41d9b7f35aadab3
the first two lines agree, so a hook nothing has registered anything into
costs the terrarium nothing: an empty valley runs the run it always ran
the third does not agree, and it is not meant to: bites and carcasses reach the ground
Twelve years is enough to prove the hook is inert and nothing like enough to prove the valley is unmoved, so the same comparison is run out to the length the terrarium closed its own books on: a thousand years, three million six hundred thousand ticks, every one of them calling a creature phase that has nobody on it.
$ 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
Both digests are the ones the valley closed on before any of this existed: the world digest over the bed's two numbers on every cell and every plant's cell and mass, and the census digest folded over all thousand midsummers. The third line of the twelve-year run is the interesting one in the other direction. Put four creatures on that roster and the digests move, and they are supposed to. Creatures eat plants and drop bodies on the ground; a valley that ran identically with animals in it would be a valley where the animals were decoration.
The store reaches zero
The phase itself is a loop with two halves, and the line between them is the design. The first half steps every creature. The second half deals with the ones that stopped.
One field goes on the creature first. The phase has to ask each animal what it wants, and the answer comes from the seam the action chapter left: twenty-four numbers in, one of six out. Two creatures standing side by side may be driven by two different things, so the answer to "what drives this one" belongs on the body and not on the loop that walks it.
// internal/beast/body.go, inside type Beast
// Mind is whatever answers for this creature when the phase asks
// it what it wants. It is a field on the body and not an argument
// to the phase because two creatures standing side by side may be
// driven by two different things, and the roster that steps them
// has no business knowing which is which.
Mind Mind
// internal/beast/roster.go, continued
// Carcass is one death, written down at the moment the store went
// under: who it was, the cell it was standing on, and the grams of body
// that are about to become litter. It is a record and not a request,
// which matters because it is made in the middle of the phase and acted
// on at the end of it.
type Carcass struct {
Who sim.EntityID
Name string
At sim.Coord
Grams float64
Tick int
}
// Phase is one creature phase: the whole roster stepped once, in
// order, and then the dead buried.
//
// Every creature is handed the same View, built once before the first
// of them moves, so all of them read the world as it stood when the
// phase opened. Each one looks, fills the row, is asked what it wants
// by whatever is driving it, and does it. Nothing in the loop touches
// anything but the creature whose turn it is.
//
// A creature whose store is at nothing when its tick closes is written
// into died and left exactly where it is. It is not removed here, and
// the reason is that removing it here would change the world underneath
// the creatures that have not been stepped yet: the View they are all
// sharing indexes it as an occupant of its cell, so striking it off in
// the middle would leave the index describing a valley that no longer
// exists, and a creature stepped ninth would see something a creature
// stepped first did not. The list is edited once, at the end, when
// nobody is reading it.
func (r *Roster) Phase(v *terra.Valley, sc Scale) (died []Carcass, refused []error) {
w := NewView(v, r.Live)
for _, b := range r.Live {
b.Look(w, r.eyes)
b.Read(&r.row, r.eyes, w, sc)
a := Rest
if b.Mind != nil {
a = b.Mind.Pick(&r.row)
}
t, err := b.Do(a, w)
if err != nil {
refused = append(refused, err)
}
r.Ate += t.Grams
r.Steps++
if t.Left <= 0 {
b.Dead = true
died = append(died, Carcass{Who: b.ID, Name: b.Name, At: b.Cell(),
Grams: b.Kind.Bulk, Tick: v.Now})
}
}
if len(died) > 0 {
r.bury(v, died)
}
return died, refused
}
The View is the load-bearing detail in that comment. It is an index of
what is standing on which cell, built once, and every creature in the phase reads the
same one. Take a creature off the roster in the middle of the loop and the index still
has it standing there, so a creature stepped ninth casts a ray at an animal that was
removed four creatures ago. Leaving the dead where they are until the loop is over is
what keeps the index honest for the whole of it.
The death test reads t.Left, which is the number the tally recorded when
that tick's last charge had gone through. There are two ledgers on a creature in this
package and only one of them is on this path. The still-life one, Ledger,
belongs to the method that charges a body against a single plant with no actions and no
movement in it; the phase never calls that method, so its closing figure is never
filled in and a test written against it would be a test of a number nothing wrote.
Do charges through Step and reports through
Tally, so Tally is where the store's last word for the tick
is.
And the creature is left exactly where it is. It is marked, it goes on a list, and the loop carries on to the next one. Nothing is removed, dropped or moved while the loop is running.
The second half is four lines of doing and one paragraph of deciding. The doing is easy: drop the grams and rebuild the list. The deciding is what a carcass is worth to the ground it lands on.
// internal/beast/roster.go, continued
// bury is the end of the phase. Every carcass of this tick goes onto
// the cell its creature was standing on, as a Pile, through the same
// terra.Bed.Fall a plant's dead tissue goes through; then the roster is
// rebuilt without the dead, in place, keeping the order of everything
// that is still walking.
//
// A carcass carries grams and no nutrient, and that is a decision
// rather than an omission. The nutrient number on a Pile is what the
// dead thing borrowed out of the bed to build itself: a plant is
// charged its Holds() the day it germinates and hands the same figure
// back the day it stops, which is why the second column closes. A
// browser never borrowed. Nothing about it is ever charged against a
// cell's nutrient, and every gram it added to itself came off a plant's
// standing mass and was spent as energy. The minerals in those grams
// went back into the ground on the tick the bite was taken, so by the
// time a carcass lands there is nothing left owing on it. Writing a
// positive nutrient figure onto it would hand the soil units nobody
// ever took out of it, which is manufacturing, and this valley does not
// manufacture.
func (r *Roster) bury(v *terra.Valley, died []Carcass) {
for _, c := range died {
v.Bed.Fall(c.At, terra.Pile{Mass: c.Grams})
r.Fell += c.Grams
r.Gone++
}
live := r.Live[:0]
for _, b := range r.Live {
if !b.Dead {
live = append(live, b)
}
}
for i := len(live); i < len(r.Live); i++ {
r.Live[i] = nil
}
r.Live = live
}
A terra.Pile holds two numbers, grams and nutrient, and they are counted
separately because they leave separately: the grams go off as the rotting burns them and
the nutrient goes down into the cell underneath. A dead plant fills in both.
Pile{Mass: c.Grams} fills in one, and leaves the other at nothing on
purpose.
The argument is about borrowing. The nutrient figure on a plant's pile is what that
plant took out of the bed to build itself with: the terrarium charges a seedling's
nutrient against the ground the day it germinates, and hands the identical figure back
to the litter the day it stops standing. It is a loan and a repayment, and that is the
reason the nutrient column closes at all. A browser never borrowed. Nothing about a
creature is ever charged against a cell's nutrient, not when it is stood up and not when
it eats; its opening forty grams are stock the run began with, and every gram it puts
inside itself afterwards comes off a plant's standing mass and is spent as energy. The
minerals that came off with those grams were handed back to the ground on the tick of
the bite, by the line in Graze that does nothing else, so a carcass arrives
with nothing owing on it.
Writing a positive nutrient figure onto its carcass would hand the soil units nobody
ever took out of it. That is manufacturing, and the rule this whole volume is written to
says the valley does not manufacture.
Which is also the reason beast.Kind does not grow a Hunger
column to hold the conversion. There is no conversion to hold. A row grows a column when
two creatures would need different values in it, and a second animal that really did
draw nutrient out of the ground would need one; the browser needs a zero, and a zero
that follows from the design does not need a field to say it.
One creature, on ground with nothing growing on it, resting every tick so that the only thing charging the store is the body itself. The row says how long that lasts before the run starts.
$ go run ./cmd/books -mode clock -found 0 -ticks 60 -every 10
books: 12x8 valley, tick 901, year 1 summer, 0 plants standing at 0.0 grams
creature 1 stood on 9,6 with 8.0000 in the store, resting every tick
a resting body earns nothing and is charged 0.1600 a tick, so the row says
the store is gone after 8.0000 / 0.1600 = 50.0 ticks
the valley was founded with no plants, so every gram of litter in it
is this creature
tick store walking roster litter rotted
10 6.4000 40.0000 1 0.0000 0.0000
20 4.8000 40.0000 1 0.0000 0.0000
30 3.2000 40.0000 1 0.0000 0.0000
40 1.6000 40.0000 1 0.0000 0.0000
50 -0.0000 0.0000 0 39.6468 0.3532
60 -0.0000 0.0000 0 36.2706 3.7294
creature 1 stopped on the phase of tick 950, standing on 9,6
40.0000 grams of body went to terra.Bed.Fall as a Pile on that cell, the same
call a dead plant's tissue goes through, and terra.Bed.Rot works on it at
the valley's own 0.0100 a tick times the warmth, the rate the plants rot at
the roster handed out 50 creature-ticks and is holding 0 creatures
handed to the litter 40.0000 grams, still lying 36.2706, rotted away 3.7294,
and those last two come to 40.0000
Fifty ticks, named in advance by dividing a store by a bill, and the store lands on
-0.0000. That is a negative zero, which is what a float64 prints when a
subtraction takes it a hair under and the hair is smaller than four decimal places can
show. The test in the phase is t.Left <= 0, and a hair under nothing is
under nothing, so the creature stops on the tick the arithmetic named rather than buying
one more.
Watch the walking and litter columns swap on that line. Forty
grams leave the roster and 39.6468 arrive in the litter, and the missing 0.3532 is not
missing: it is in the rotted column, on the same row. The carcass fell before
the weather ran, so the rotting at the bottom of that same tick took its first share of it
immediately, exactly as it would have from a plant that died on the same tick. Ten ticks
later 3.7294 grams have burned off and 36.2706 are still lying there, and the two of them
add back to 40.0000. That is what the placement of one line in
terra.Valley.Tick bought.
An audit of a closed world is one subtraction. Add up everything that ever went in, add up every place a gram can be now, and take one from the other. If the answer is not nothing, some line is missing.
Three things go in. The valley was founded with plants standing in it, which for the run at the end of this chapter comes to 11.250000 grams. Its leaves manufactured 43,613.746056 grams out of light over two years. And twelve creatures were stood up at 40 grams a body, which is 480.000000. Those three add to 44,104.996056 grams, and nothing else has ever entered.
Nine places a gram can be. Standing in a living plant, 2,361.841868. Walking about in a living body, 0.000000, because none of the twelve is left. Flying as a seed, 0.000000. Waiting in the seed bank, 286.000000. Lying in the litter, 1.057533. Burned as plant upkeep, 38,432.484261. Rotted off the litter, 659.660979. Blown over the rim, 77.000000. And eaten, 2,286.951415, which is grams a mouth took off a stand and a store spent as energy. Those nine add to 44,104.996056 as well, and the subtraction leaves 0.000000.
To the last bit it leaves −7.421e−10 instead of exactly nothing, and that residue is the arithmetic and not the model. Forty-four thousand grams accumulated over six thousand seven hundred and fifty ticks of adding tenths and hundredths to a float64 cannot land on a round number, and a difference of seven ten-thousand-millionths on a total of forty-four thousand is fourteen significant figures of agreement. A ledger that came out at exactly 0 would mean somebody was rounding.
in = O + B + S
out = p + c + a + k + l + u + r + w + e
in − out = 0
Twelve dead browsers
One creature dying says nothing about the order deaths are applied in, because with one creature there is no order. Twelve of them with identical stores and identical bills all run out on the same tick, and that is the case the rule was written for.
$ go run ./cmd/books -mode phase -herd 12 -ticks 55
books: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams
12 creatures on stream 12, every one of them resting with 8.0000 in the store
a rest costs nothing and the body costs 0.1600, so every one of them
runs out on the same tick: 50.0
deaths are applied at the end of the phase, when nobody is reading the roster
tick roster stepped walking in the litter
10 12 12 480.0000 0.0000
20 12 12 480.0000 0.0000
30 12 12 480.0000 0.0000
40 12 12 480.0000 0.0000
50 0 12 0.0000 475.7615
55 0 0 0.0000 455.0853
12 creatures struck off, 480.0000 grams of carcass handed to the litter
the roster handed out 600 creature-ticks over 55 ticks
The row for tick 50 is the whole rule in its middle two columns. The roster is empty when the tick ends, and the phase inside that tick handed a tick to twelve creatures. Both are true at once, and they can only both be true if the list was edited after the loop had finished with it. Six hundred creature-ticks over fifty-five ticks is twelve of them for fifty ticks and none for the five after, which is the same fact counted from the other end.
The litter stops short of 480, at 475.7615, and that is the rotting again, taking its share of twelve piles on the tick they landed. Five ticks later it is down to 455.0853 and the valley is quietly turning twelve browsers back into soil.
Collecting the dead into a list and dealing with them afterwards looks like ceremony. The obvious version does the work where the work is discovered: the store goes under, drop the grams, take the creature off the list, carry on. Nothing has to be remembered between the discovery and the doing, and no second list of anything exists at any point.
// internal/beast/roster.go, inside Phase, with Hasty set
if t.Left <= 0 {
b.Dead = true
c := Carcass{Who: b.ID, Name: b.Name, At: b.Cell(),
Grams: b.Kind.Bulk, Tick: v.Now}
died = append(died, c)
if r.Hasty {
r.bury(v, died[len(died)-1:])
}
}
The flag sits on the roster beside the totals and is false in every roster this book ships, so the failure can be run instead of described. Same twelve creatures, same stores, same tick.
$ go run ./cmd/books -mode phase -herd 12 -ticks 55 -hasty
books: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams
12 creatures on stream 12, every one of them resting with 8.0000 in the store
a rest costs nothing and the body costs 0.1600, so every one of them
runs out on the same tick: 50.0
deaths are applied the instant the store goes under, in the middle of the phase
tick roster stepped walking in the litter
10 12 12 480.0000 0.0000
20 12 12 480.0000 0.0000
30 12 12 480.0000 0.0000
40 12 12 480.0000 0.0000
the tick loop stopped on tick 50 and the run went no further
panic: runtime error: invalid memory address or nil pointer dereference
12 creatures were on the roster when that phase opened; it gave a tick to
6 of them and then reached a slot the burial had already blanked, leaving
6 creatures standing that nothing has asked anything of
Forty-nine ticks of a table that reads exactly like the good one, then a nil pointer.
Reason it out from the symptom. The panic is a nil *Beast, and there is
only one place in this package that ever writes a nil into that slice: the loop at the
bottom of bury that blanks the tail after compacting. So the burial and the
walk are looking at the same array at the same time, and the count in the last line says
how the collision goes. Six is half of twelve, and half is not a coincidence. Every
iteration buries one creature, which shortens the live part of the array by one and
blanks one more slot from the end; meanwhile the loop index moves up by one. The two
meet in the middle, at index six of twelve.
The compaction is the part that makes it a nil and not merely wrong. live :=
r.Live[:0] gives a slice that shares its backing array with the one the
range is walking, so appending survivors into it overwrites the very
entries the loop has not reached yet. Six of the twelve get shifted down past the index
the loop is at, and are never asked what they want to do. They are not dead, they are
not stepped, and if the walk had run out of creatures before it ran out of blanks they
would have gone on standing there for the rest of the run, paying nothing and eating
nothing, and no column anywhere would have said so.
The general rule is older than this valley and has nothing to do with animals.
Do not edit a collection while something is iterating it. Go will not stop you,
because a slice is a window onto memory and both the walk and the edit are perfectly
legal operations on that memory. What saves you is putting the edits somewhere until the
walk is over, which is what the died slice is. It exists so that the answer
to "what changed during this phase" is computed during the phase and applied after it,
and the phase itself only ever reads.
Why the audit has to close
Let the twelve loose on the valley properly this time, with full stores and the rule that hunts plants, and let the year run over them.
$ go run ./cmd/books -mode run -herd 12 -store 400 -years 2
books: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams
12 creatures on stream 12, 400.0000 in every store, each one driven by
the rule that turns to the nearest plant ray, walks it down and bites
the creature phase is registered into terra.Valley.Phase and runs once a
tick, after the plants have eaten and before the rain reaches the ground
tick season alive eaten carcass standing litter
1351 summer 12 581.1966 0.0000 188.24 0.3807
2251 autumn 12 1872.5646 0.0000 299.52 6.1668
3151 winter 12 2286.9514 0.0000 0.00 1.5007
4051 spring 0 2286.9514 480.0000 1632.36 96.0403
4951 summer 0 2286.9514 480.0000 638.43 0.0588
5851 autumn 0 2286.9514 480.0000 1564.64 27.9796
6751 winter 0 2286.9514 480.0000 1363.98 5.4533
7651 spring 0 2286.9514 480.0000 2361.84 1.0575
12 creatures started, 12 struck off, 0 still walking
29307 creature-ticks handed out over 6750 ticks of valley
Read the standing column down and the story is not subtle. The valley held
1,517.5 grams of plant on the tick the herd was let into it. Four hundred and fifty ticks
later it holds 188.24, and 581 of the missing grams are inside twelve browsers. Autumn is
the herd's quarter: the plants put mass on faster than the mouths take it off, the
browsers still get 1,291 grams out of the quarter, more than twice what the whole of
summer gave them, and the valley ends it standing higher than it began it. Then winter.
Every plant that was still up comes down, standing reads 0.00, and the
eaten column arrives at 2,286.9514 and never moves again, because there is
nothing left in the valley to bite. All twelve are alive on that line and none of them is
on the next one. Nothing in the model was tuned to produce that. A browser cannot keep a
season's worth of energy in a store that holds 400 units against a body burning 0.16 of
them a tick, and there is nowhere else in this world for food to be kept.
Then the half that is not about starving. With the herd gone the valley climbs back past 1,600 grams by that spring and past 2,300 by the second one, and the 480 grams that used to be twelve animals go into the litter and out of it again as the rotting works through them. Everything above is a summary, though, and a summary is where a missing gram hides. The audit is the block the run finishes on.
$ go run ./cmd/books -mode run -herd 12 -store 400 -years 2
the books in grams
the valley opened with 11.250000 plants founded on the first tick
built out of light 43613.746056 every gram the leaves ever fixed
creatures stood up 480.000000 bodies the run opened with
in 44104.996056
standing 2361.841868 living plant tissue
walking 0.000000 living creature bodies
in the air 0.000000 seeds still flying
in the bank 286.000000 seeds waiting in the ground
lying dead 1.057533 litter: plants and carcasses together
burned as upkeep 38432.484261 grams a plant spent on standing there
rotted 659.660979 grams the litter has burned off
blown away 77.000000 seeds that left over the rim
eaten 2286.951415 grams a mouth took and a store spent
out 44104.996056
difference -0.000000 -7.421e-10, which is the last bits of the adding
the two lines the creature phase owns, checked against each other
12 creatures struck off at 40 grams a body is 480.000000 grams of carcass
handed to terra.Bed.Fall 480.000000 grams
difference 0.000000 grams
Three lines on that page are the creature phase's and the rest were the terrarium's
already. creatures stood up is the stock the run began with.
walking is what is left of it in living bodies. eaten is grams
that came off a plant, went into a store as energy and were spent, which makes it a sink
in exactly the way burned is a sink: the gram is gone and the ledger says
where it went. There is no carcass line, because a carcass is not a place a
gram can be. It is a gram moving from walking into lying dead,
and both of those columns were already there.
The bottom three lines are the check that the middle of the ledger cannot make. A total
that balances is not the same as a total that is right, because a wrong number in one
column and its mirror image in another balance perfectly. So the carcasses are counted a
second way, from an entirely different direction: twelve creatures struck off, at the
bulk written in the row they were stamped from, is 480 grams that ought to have gone
through terra.Bed.Fall, and 480.000000 went through it. That one is exact
rather than nearly exact, because it is twelve additions of the same whole number and
nothing has divided anything.
walking, and one
arrow out of it, and that arrow goes to a box the plants were already using.
The generalisation is not about valleys. A ledger like this only tells you anything if
the world it covers is closed, meaning every route in and out is a line on the page. The
moment one is missing, the difference column stops being a check and becomes a place for
the error to live: it will still print a number, and the number will be whatever the
missing route happens to have moved. That is why blown away is on the page
even though those grams left the valley for ever, and why eaten is on it
even though the grams became heat. Both are exits, both are counted, and the total that
has to match is the one at the top.
The mineral count
That difference column closed to fourteen significant figures and it was still not the whole truth about the run. An audit checks the things it has columns for, and every column above is grams. The terrarium has kept a second set of books since it learned to rot, counted in nutrient units, and the creature phase has no line in them anywhere.
Those books work by lending. A plant is charged nutrient against the cells its roots reach
as it grows, at its row's Hunger for every gram it puts on, and
Holds() is what it is carrying at any moment: standing mass times that rate.
The day it stops standing, the identical figure goes into the litter on its cell and rots
down into the ground underneath. A loan and a repayment, and that is the only reason the
second column has ever closed.
A mouth is the thing that arrangement was never written for. A bite takes standing tissue
off a plant, and standing tissue is holding minerals the plant borrowed to build it. The
grams have somewhere to go: they become energy, a body burns them, and eaten
is the line that says so. The minerals have nowhere. A creature is never charged nutrient,
not for the body it was stood up with and not for a mouthful, so it has no account for
minerals to sit in.
So the bite hands them back where they came from: on the tick a mouth closes, the
nutrient that mouthful was carrying lands on the cell the plant is standing on, through
the same terra.Bed.Fall the plant's own burned upkeep has always gone
through. The loan belonged to the plant, and it is settled the moment the tissue
leaves the plant, whether it left by dying or by being eaten.
-mode dirt adds that column up over the same two years, once for the valley
with the twelve in it and once for the valley with nobody.
$ go run ./cmd/books -mode dirt -herd 12 -store 400 -years 2
books: the nutrient books over 2 years, the same valley twice
400.0000 in every store, the rule that hunts the nearest plant
no creatures 12 browsers
the valley opened with 528.000000 528.000000 soil cells full of it on the first tick
in living tissue 226.782456 213.310767 drawn out of the ground and still standing
in the air 0.000000 0.000000 locked up in seed still flying
in the bank 23.320000 19.680000 locked up in seed waiting in the ground
lying in the litter 257.597260 249.259994 dead tissue that has not rotted down yet
in the soil 13.760284 40.769239 back in the cells, ready to be drawn again
blown away 6.540000 4.980000 left over the rim inside a seed
out 528.000000 528.000000
difference -0.000000 0.000000
eaten 0.000000 2286.951415 grams a mouth took off a stand
residue -2.274e-13 6.821e-13 the last bits of the adding
Both columns come to 528.000000, and that opening figure was not measured either: this
valley has forty-four cells a root can live in, twelve units were put in each of them on
the first tick, and there is no route into this world for a forty-fifth. The residues are
printed instead of rounded off for the reason the grams residue is printed. Minus
2.274e−13 on the empty valley and 6.821e−13 on the grazed one are the last bits
of the adding, and a column that came out at exactly zero would mean somebody had been
tidying. The eaten line underneath is in grams rather than units and is there
to tie the two pages together: 2,286.951415 is the same figure the audit printed, taken
off the same stands on the same ticks.
Now stop reading the totals and read across the rows, because they do not match and are not meant to. The grazed valley holds less in living tissue, 213.310767 against 226.782456, less in the litter, and nearly three times as much in the soil: 40.769239 units of free mineral against 13.760284. That is what a mouth does to this ledger. Tissue a browser eats gives its minerals up on the tick of the bite; tissue nothing eats keeps them until the plant dies, then keeps them again as litter until the rotting has worked through the pile, which takes most of a season. Grazing shortens the route a mineral takes back to the ground. It takes none out of The Hollow.
The size of that soil row is why the shortcut matters. Fourteen units spread over forty-four cells is what the ungrazed valley has free at the end of two years, and everything else it owns is locked up inside something. When the circulating pool is that thin, how fast minerals come back to it is what decides how much the leaves can build, because a root that reaches no nutrient has a plant that is nutrient-limited whatever the sky is doing. So the arithmetic runs the way it does not sound as though it should. Twelve browsers take 2,286.951415 grams out of this valley over two years, and the valley builds more than it would have built if they had never been in it.
$ go run ./cmd/books -mode run -herd 0 -store 400 -years 2
the books in grams
the valley opened with 11.250000 plants founded on the first tick
built out of light 43137.313190 every gram the leaves ever fixed
creatures stood up 0.000000 bodies the run opened with
in 43148.563190
standing 2320.368868 living plant tissue
Same seed, same weather, same founding generation, nothing walking about in it: 43,137.313190 grams built out of light, 2,320.368868 standing at the end. With the twelve in it the leaves built 43,613.746056 and 2,361.841868 was left standing, so the grazed valley finished 476.432866 grams ahead on what it manufactured and 41.473000 grams ahead on what it was holding, and fed a herd on the way. Nothing was tuned to get that. Chewing tissue up and dropping its minerals on the ground is a faster way to get them back into circulation than waiting for the plant that grew them to die.
One line of Graze is doing all of it, and the creature carries a flag that
switches it off, so this is a run and not a claim. Unpaid is named for the
ground and not for the animal, because a creature carrying it eats exactly what it always
ate and every unit that goes missing goes missing from the bed. Set it and the grams still
leave the plant; the minerals they were holding land nowhere.
$ go run ./cmd/books -mode dirt -herd 12 -store 400 -years 2 -unpaid
books: the nutrient books over 2 years, the same valley twice
400.0000 in every store, the rule that hunts the nearest plant
a bite hands the minerals it took back to no cell
no creatures 12 browsers
the valley opened with 528.000000 528.000000 soil cells full of it on the first tick
in living tissue 226.782456 107.847762 drawn out of the ground and still standing
in the air 0.000000 0.000000 locked up in seed still flying
in the bank 23.320000 18.450000 locked up in seed waiting in the ground
lying in the litter 257.597260 152.110345 dead tissue that has not rotted down yet
in the soil 13.760284 74.951173 back in the cells, ready to be drawn again
blown away 6.540000 3.720000 left over the rim inside a seed
out 528.000000 357.079280
difference -0.000000 170.920720
eaten 0.000000 2051.824604 grams a mouth took off a stand
residue -2.274e-13 1.709e+02 the last bits of the adding
357.079280 of 528. That is 170.920720 units gone, a third of everything The Hollow has ever had, with no line on any page to say where. Read down the browsers' column and there is nowhere it could be hiding: less standing, less in the bank, less in the litter, less blown over the rim, and the free soil pool up instead of down, because a valley short of a third of its minerals grows less tissue to lock the rest of them into. The bottom line is the one to look at hardest. That residue column exists to print the last bits of the adding, a rounding crumb in the region of 1e−13, and on this run it reads 1.709e+02: the hole itself, printed in the notation the page reserved for noise.
Now run the grams over the same two years with the same flag set.
$ go run ./cmd/books -mode run -herd 12 -store 400 -years 2 -unpaid | tail -8
eaten 2051.824604 grams a mouth took and a store spent
out 35397.770577
difference 0.000000 3.711e-10, which is the last bits of the adding
the two lines the creature phase owns, checked against each other
12 creatures struck off at 40 grams a body is 480.000000 grams of carcass
handed to terra.Bed.Fall 480.000000 grams
difference 0.000000 grams
Fourteen significant figures, and the carcass check agrees with itself to the gram. That audit is as clean with a third of the valley's minerals missing as it was without. The audit that was being read every time said zero, because the units it was adding up were not the units going missing.
So the generalisation the grams audit started has a second half. A closed-world check is only closed in the currency it counts. Grams and minerals leave a plant together and then travel apart, so anything that can move one of them without the other needs both columns totalled, or the untotalled one becomes where the error lives. It will not announce itself. The books somebody is adding up go on coming out at zero.
- Say why the running totals for grams eaten and grams buried live on the roster and not on the creature, and what breaks in the ledger if they live on the creature.
- Given the browser row and a starting store, name the tick a resting creature
stops on before running it, and say why a store printing
-0.0000satisfies a test written as<= 0. - Defend the zero in
Pile{Mass: c.Grams}by saying where the minerals in a mouthful had already gone, and say what a second kind of creature would have to do to the ground before the row needed a column for it. - Shown a phase that steps twelve creatures and ends with an empty roster, say which of those two happened first and why the other order panics at index six.
- Name the three lines the creature phase adds to the valley's grams and say which of them is a sink, and say what a grams ledger closing to fourteen figures still fails to prove about the nutrient books.
- Explain why the carcass total is checked a second time against the count times the row's bulk, when the difference column already reads zero.
Exercise 1 — give them a bigger store. The herd in the
phase run all stop on tick 50 because they all open with 8.0000. Predict the tick for
a store of 20, then check it with
go run ./cmd/books -mode phase -herd 12 -ticks 140 -store 20.
Tick 125, and the run says so on its third line before the table starts: 20 divided by the 0.1600 a forty-gram body costs a tick. What does not change is the more useful half. Twelve are still stepped on the phase they die on, 480.0000 grams are still handed to the litter, and the list is still edited once, after the loop. The store decides when, and nothing about when touches the order.
$ go run ./cmd/books -mode phase -herd 12 -ticks 140 -store 20 | tail -8
110 12 12 480.0000 0.0000
120 12 12 480.0000 0.0000
125 0 12 0.0000 475.5759
130 0 0 0.0000 454.0273
140 0 0 0.0000 413.6711
12 creatures struck off, 480.0000 grams of carcass handed to the litter
the roster handed out 1500 creature-ticks over 140 ticks
Exercise 2 — take the plants away and watch the ledger.
Run the audit on a valley founded with nothing growing in it:
go run ./cmd/books -mode run -herd 12 -store 400 -years 1 -found 0. Which
columns go to zero, and does the difference still close?
Everything on the plant side goes to nothing: nothing was founded, nothing was built out of light, nothing is standing, nothing is in the bank, nothing blew away and nothing was burned as upkeep. What is left is 480 grams of body going into the litter and burning away again, which after a year is 479.939951 rotted and 0.060049 still lying. It closes, but look at the residue: −2.274e−13 on a total of 480, against −7.421e−10 on a total of forty-four thousand. A smaller ledger has fewer additions in it and loses fewer bits doing them.
$ go run ./cmd/books -mode run -herd 12 -store 400 -years 1 -found 0 | tail -15
walking 0.000000 living creature bodies
in the air 0.000000 seeds still flying
in the bank 0.000000 seeds waiting in the ground
lying dead 0.060049 litter: plants and carcasses together
burned as upkeep 0.000000 grams a plant spent on standing there
rotted 479.939951 grams the litter has burned off
blown away 0.000000 seeds that left over the rim
eaten 0.000000 grams a mouth took and a store spent
out 480.000000
difference -0.000000 -2.274e-13, which is the last bits of the adding
the two lines the creature phase owns, checked against each other
12 creatures struck off at 40 grams a body is 480.000000 grams of carcass
handed to terra.Bed.Fall 480.000000 grams
difference 0.000000 grams
Exercise 3 — make the tests say it. The package carries
four claims about this chapter as tests. Run
go test ./internal/beast/ -run 'Hook|Phase|Carcass|Books' -v and match
each name to the paragraph in this chapter that argues for it.
The first is the hook being inert, which is the claim the whole volume rests on. The second is the phase-order rule, asked as a count: twelve creatures on the roster, twelve ticks handed out on the phase they die on. The third is the mass rule for the one transfer this chapter adds, checking that the litter plus everything rotted off it comes to the bodies struck off and nothing else. The fourth is both ledgers at once, run on a valley that is really being eaten: the grams have to close, and so do the minerals, and it is the second of those a bite can quietly break.
$ go test ./internal/beast/ -run 'Hook|Phase|Carcass|Books' -v
=== RUN TestAnEmptyHookLeavesTheTickAlone
--- PASS: TestAnEmptyHookLeavesTheTickAlone (0.92s)
=== RUN TestEveryCreatureGetsItsTickOnThePhaseItDiesOn
--- PASS: TestEveryCreatureGetsItsTickOnThePhaseItDiesOn (0.00s)
=== RUN TestEveryGramOfCarcassReachesTheBed
--- PASS: TestEveryGramOfCarcassReachesTheBed (0.00s)
=== RUN TestTheBooksCloseOnAValleyBeingEaten
--- PASS: TestTheBooksCloseOnAValleyBeingEaten (0.32s)
PASS
ok theworld/internal/beast 1.245s
Twelve browsers ate a valley down to nothing and starved in its first winter, and the only thing choosing anything for them was twenty lines of hand-written rule that turns toward the nearest plant its rays can see. Nothing in the apparatus cares that the rule is hand written. The seam it plugs into takes twenty-four numbers and returns one of six, and the next thing to sit behind that seam is arithmetic: multiply a number by a weight, add the results together, and decide.