The Row Becomes a Genome
Genome factors on the founding row
Every creature walking through The Hollow is stamped out of the same ten numbers: forty grams of body, four hundred energy units of store, four thousandths of a unit a gram a tick to stand there, a quarter of a gram a bite, four units a gram once food is inside, twelve cells of sight, 0.45 of a cell at a sprint, and a twelfth of a turn a turn. A gene is a factor on the founding row and never a value of its own.
The factor says what to multiply by, so the whole genome is unitless. The founding row stays the one place in the world where a gram is priced, and a genome of all ones stamps that row back out unchanged, bit for bit.
Two creatures in this valley differ in a short list of ways. They stand on different cells, face different ways, hold different stores, carry different eaten totals, and own different sets of three hundred and seventy-eight weights drawn off stream 14. Every animal has been the same body driven by a different brain.
That list gives inheritance almost nothing to use. A position is a state, a store is a balance, and weights alone would make a lineage of identical bodies for ever. Every question this volume asks about what a body is worth would have exactly one answer.
Giving each animal its own copy of the row looks easier and fails in two places. A row mixes grams, energy units, cells and fractions of a turn, so any imperfect copy has to know what each entry means before it can decide how far far is.
It also scatters away from the only price already defended by a run: a bite of a quarter of a gram at four units a gram, against an upkeep of 0.16 a tick, made a browser a brake on the scrub instead of a plague on it. A factor keeps that price visible while still letting a child be born different.
By the end of the page there is a package called internal/gene holding
Genome, its four blocks, the range every one of its 395 numbers is held inside,
and one method that turns a genome into a creature row. There is one new field on the
roster, and it is the only thing internal/beast gains in the whole of this
volume.
Three runs prove that none of it moved anything: the same thousand years of ground, the same nineteen animals founded and the seventeen of them that starve doing it on the same ticks, the same three-thousand-creature phase closing on the same sixteen characters. A genome wrapped round a row it does not change has to be invisible; otherwise the bug is in the streams.
The Body, Mind, Temper and Look blocks
A genome here holds four kinds of thing, and they go in a fixed order that nothing is ever
allowed to change: BODY, MIND, TEMPER, LOOK. Fixed order matters more than it sounds.
Everything that copies a genome, crosses two of them or moves one number in one walks the
whole thing as a flat run of numbers, and two runs of this world that disagree about which
position Sight sits at are two different worlds wearing the same seed.
The BODY block is ten factors in the founding row's own field order. The MIND block is the controller: the same three hundred and seventy-eight numbers a brain has always been, in the same flat order the network reads them in. TEMPER is three numbers, and no code anywhere in this valley reads any of them; they are here because a record of a lineage cannot be given a new column after its ancestors are dead, and this is the moment when nothing has died yet. LOOK is four numbers that decide what a creature is drawn as.
// internal/gene/gene.go
package gene
// The ten body factors, in beast.Kind's own field order. The order is
// the contract: a genome is walked as a flat run of numbers by
// everything that copies, crosses or mutates one, and two runs that
// disagree about where Sight sits are two different worlds.
const (
Bulk = iota
Full
Basal
Work
Bite
Convert
Reach
Sight
Top
Swing
Bodies
)
// The three temperament genes, in order.
const (
Aggression = iota
Wariness
Tameness
Tempers
)
// The four look genes, in order.
const (
Coat = iota
Mark
Cover
Part
Looks
)
// Hidden is the middle row of the controller a MIND block describes.
// It is the size the sensor row and the action table were wired to,
// and it is written down here because the block's length is worked out
// from it.
const Hidden = 12
// Weights is how many numbers a MIND block holds: every weight and
// bias of a network of the fixed shape, in that network's own flat
// order.
func Weights() int { return mind.Weights(beast.Inputs, Hidden, beast.Acts) }
// Span is the range one gene is allowed to hold. Every gene has one,
// no gene is allowed outside its own, and the two ends are inclusive.
type Span struct{ Lo, Hi float64 }
// Clamp holds a number inside the span. A gene that would leave its
// range stops at the edge instead of leaving, so no arithmetic
// anywhere can produce a creature this world has no prices for.
func (s Span) Clamp(x float64) float64 {
if x < s.Lo {
return s.Lo
}
if x > s.Hi {
return s.Hi
}
return x
}
// Wide is how far the span runs end to end.
func (s Span) Wide() float64 { return s.Hi - s.Lo }
var (
BodySpan = Span{Lo: 0.25, Hi: 4.00}
SightSpan = Span{Lo: 0.50, Hi: 2.00}
MindSpan = Span{Lo: -4.00, Hi: 4.00}
UnitSpan = Span{Lo: 0.00, Hi: 1.00}
)
Nine of the ten body factors run from a quarter to four. Those two ends are the same step
in opposite directions: four is one multiplication by four above 1.00, and 0.25 is one
division by four below it, so nothing in this world can drift more easily towards a big
body than towards a small one. Sight is the exception and gets half the
leash in both directions, and the reason is arithmetic rather than biology. Nothing else
in the row costs the machine anything to be large: a heavier creature is a bigger number
in a subtraction. A line of sight is walked, one short sample at a time, and every
creature walks nine of them every tick. Double the sight cap and you have doubled the
samples. The eyes are the largest single cost the creature phase carries, the phase has
fifty milliseconds of a hundred, and a factor of four on Sight would be four
times the ray steps the budget was drawn up against. Half the range for the one gene the
clock can feel.
// internal/gene/gene.go, continued
// Genome is the whole of what one creature inherits, in four blocks and
// one fixed order: BODY, MIND, TEMPER, LOOK.
type Genome struct {
Body [Bodies]float64
Mind []float64
Temper [Tempers]float64
Look [Looks]float64
}
// BodyAt, MindAt, TemperAt and LookAt are where each of the four blocks
// starts when a genome is walked as one flat run of numbers, and Len is
// how many numbers that walk covers. Nothing else anywhere works out an
// offset into a genome, for the reason a network's four block offsets
// are written down in one place and never recomputed.
func (g *Genome) BodyAt() int { return 0 }
func (g *Genome) MindAt() int { return Bodies }
func (g *Genome) TemperAt() int { return Bodies + len(g.Mind) }
func (g *Genome) LookAt() int { return g.TemperAt() + Tempers }
func (g *Genome) Len() int { return g.LookAt() + Looks }
// At is the i'th gene of the flat walk.
func (g *Genome) At(i int) float64 {
switch {
case i < g.MindAt():
return g.Body[i]
case i < g.TemperAt():
return g.Mind[i-g.MindAt()]
case i < g.LookAt():
return g.Temper[i-g.TemperAt()]
}
return g.Look[i-g.LookAt()]
}
// Put writes the i'th gene, clamped into that gene's own range.
//
// The clamp is here and not in whatever did the writing. A range
// enforced by the caller is a range every future caller has to remember,
// and the number that would catch a forgotten one is already in the
// genome by the time anybody looks. Written here, there is no road into
// a genome that skips it.
func (g *Genome) Put(i int, x float64) {
x = g.Span(i).Clamp(x)
switch {
case i < g.MindAt():
g.Body[i] = x
case i < g.TemperAt():
g.Mind[i-g.MindAt()] = x
case i < g.LookAt():
g.Temper[i-g.TemperAt()] = x
default:
g.Look[i-g.LookAt()] = x
}
}
// Span is the range the i'th gene is allowed to hold.
func (g *Genome) Span(i int) Span {
switch {
case i == Sight:
return SightSpan
case i < g.MindAt():
return BodySpan
case i < g.TemperAt():
return MindSpan
}
return UnitSpan
}
$ go run ./cmd/stamp -mode row
stamp: the genome, four blocks and 395 numbers, in one fixed order
block at genes one number of it is range
BODY 0 10 a factor on the founding row 0.25 to 4.00
MIND 10 378 a weight of the controller -4.00 to 4.00
TEMPER 388 3 a share, inherited and unread 0.00 to 1.00
LOOK 391 4 a share, read when it is drawn 0.00 to 1.00
10 + 378 + 3 + 4 = 395
the BODY block, in the founding row's own field order:
i gene the row range at 1.00 what the number is
0 Bulk 40.0000 0.25 to 4.00 40.0000 grams of body
1 Full 400.0000 0.25 to 4.00 400.0000 energy units the store holds full
2 Basal 0.0040 0.25 to 4.00 0.0040 energy units a gram of body costs a tick
3 Work 0.6000 0.25 to 4.00 0.6000 energy units one unit of movement work costs
4 Bite 0.2500 0.25 to 4.00 0.2500 grams one bite takes off a stand
5 Convert 4.0000 0.25 to 4.00 4.0000 energy units a gram of eaten tissue is worth
6 Reach 1.0000 0.25 to 4.00 1.0000 cells a bite reaches
7 Sight 12.0000 0.50 to 2.00 12.0000 cells one line of sight runs
8 Top 0.4500 0.25 to 4.00 0.4500 cells a tick at a sprint
9 Swing 0.0833 0.25 to 4.00 0.0833 the part of a whole turn one turn covers
the temperament genes are aggression, wariness, tameness, and nothing reads any of them
the look genes are coat hue, marking hue, marking coverage, part variant
every number above is a factor: the genome is in no units at all
Two things in that listing are doing more work than their line length suggests. The four
At methods are the only place in this world that works out where a block
starts; every other piece of code asks them. A network's weights are laid out the same
way, four blocks in one flat slice with four small methods naming the boundaries, and the
rule there is the rule here: an offset computed in two places is an offset that will
disagree with itself the first time a block changes length. The other is
Put. It clamps, which means a genome cannot hold a number outside its own
range no matter what wrote to it or how carelessly. Range checking done by callers is
range checking that has to be remembered, and the run that would catch a forgotten one is
the run where a creature already has a sight cap of ninety cells.
Take the first entry of the row. The founding value is 40 grams. Store the gene as a value and a creature carrying 80 has the number 80 written in it. Store it as a factor and the same creature carries 2.00, and the 80 is worked out when it is needed: 40 × 2.00.
Write the founding value r and the gene f, and the number the creature runs on is v:
v = r × f
Three things follow from that one line, and every one of them is the reason this volume is arranged the way it is. First, f = 1.00 gives back r exactly. Multiplying by one is the arithmetic that changes nothing, so a genome of all ones is a creature identical to the one the valley already has, and "identical" here means the same bits and not a number that rounds to the same printed digits.
Second, f has no units. r is in grams for Bulk, energy units for
Full, cells for Sight and a fraction of a turn for
Swing. f is a bare number in all ten cases, so an operator that copies a
genome imperfectly can treat all 395 of its numbers the same way and never has to know
what any of them means.
Third, the range. 0.25 and 4.00 are the same distance from 1.00 when the distance is
counted in multiplications: 1.00 × 4 = 4.00 and 1.00 ÷ 4 = 0.25. Written as
values that pair would be 160 grams and 10 grams, which are 120 above and 30 below,
and a drift with no preference in it would look like a drift towards fat creatures.
Sight's pair is the same trick at half the size: 2.00 and 0.50 are one
doubling and one halving away from the middle.
The cost of a sight cap is the one piece of arithmetic here that is not obvious. A ray is walked in samples a quarter of a cell apart and there are nine rays in a fan, so the samples one creature's eyes cost in a tick are
steps = Fan × (S × f) ÷ Stride
which at the founding row is 9 × 12 ÷ 0.25 = 432, and at any other factor is
just 432 × f. That is a straight line through the origin: nothing about it flattens
out. At the largest Sight a genome can hold it is 864 samples a creature a
tick, and at a factor of four it would have been 1,728. The clock is the reason for the
shorter leash, and a run below asks for the top of it.
Stamping a row out of a genome
A creature row is a beast.Kind, a flat struct of ten numbers and a name, and
Kind.Spawn already copies one into a creature so that nothing an animal does
can reach back and edit the kind it came from. Stamping is the step before that: take a
row, multiply its ten numbers by the ten factors standing over them, hand back a row.
// internal/gene/gene.go, continued
// Identity is the genome that changes nothing: every body factor at
// 1.00, every temperament and look gene at the middle of its range,
// and a MIND block of the right length with every number left at zero.
func Identity() *Genome {
g := &Genome{Mind: make([]float64, Weights())}
for i := range g.Body {
g.Body[i] = 1
}
for i := range g.Temper {
g.Temper[i] = 0.5
}
for i := range g.Look {
g.Look[i] = 0.5
}
return g
}
// Founder is one founding creature's genome: the identity genome with a
// brain drawn off the generator handed in.
//
// A founder's ten body factors are not drawn. They are all 1.00, so
// every creature a valley opens with is stamped out of the founding row
// exactly as it was before there were genomes at all, and the only
// numbers this founding spends are the ones the brains were already
// spending. Where the founding differences come from is the run and not
// the founding.
func Founder(r *rand.Rand) *Genome {
g := Identity()
n := mind.New(beast.Inputs, Hidden, beast.Acts)
n.Draw(r)
copy(g.Mind, n.W)
return g
}
// Copy is a genome nothing shares with the one it came from. The MIND
// block is a slice, so copying the struct alone would hand two genomes
// one set of weights and every change to either would be a change to
// both.
func (g *Genome) Copy() *Genome {
out := *g
out.Mind = make([]float64, len(g.Mind))
copy(out.Mind, g.Mind)
return &out
}
// Stamp turns a genome into the row one creature is run off: the row
// handed in, with each of its ten numbers multiplied by the factor
// standing over it. The row goes in by value and comes out by value, so
// stamping a thousand creatures out of beast.Fauna[0] leaves
// beast.Fauna[0] exactly as it was found.
//
// Every factor at 1.00 hands the row back untouched, and untouched
// means bit for bit: multiplying a float64 by exactly 1.0 is required
// to return that float64, so the identity case is not a near miss that
// rounds well.
func (g *Genome) Stamp(k beast.Kind) beast.Kind {
k.Bulk *= g.Body[Bulk]
k.Full *= g.Body[Full]
k.Basal *= g.Body[Basal]
k.Work *= g.Body[Work]
k.Bite *= g.Body[Bite]
k.Convert *= g.Body[Convert]
k.Reach *= g.Body[Reach]
k.Sight *= g.Body[Sight]
k.Top *= g.Body[Top]
k.Swing *= g.Body[Swing]
return k
}
// Net is the controller the MIND block describes: a network of the
// fixed shape with the block's numbers copied into its weights. The
// numbers are copied and not shared, for the reason a stamped row is a
// copy: a brain is a phenotype, and nothing a phenotype does may reach
// its genotype.
func (g *Genome) Net() mind.Net {
n := mind.New(beast.Inputs, Hidden, beast.Acts)
n.Squash = mind.Tanh
copy(n.W, g.Mind)
return n
}
$ go run ./cmd/stamp -mode stamp
stamp: the identity genome against the founding row
i gene factor the row stamped the same bits
0 Bulk 1.0000 40.0000 40.0000 yes
1 Full 1.0000 400.0000 400.0000 yes
2 Basal 1.0000 0.0040 0.0040 yes
3 Work 1.0000 0.6000 0.6000 yes
4 Bite 1.0000 0.2500 0.2500 yes
5 Convert 1.0000 4.0000 4.0000 yes
6 Reach 1.0000 1.0000 1.0000 yes
7 Sight 1.0000 12.0000 12.0000 yes
8 Top 1.0000 0.4500 0.4500 yes
9 Swing 1.0000 0.0833 0.0833 yes
10 of 10 stamped to the bit
what the row works out the row stamped
the bill, energy a tick 0.1600 0.1600
ticks a full store covers 2500.0000 2500.0000
ticks one bite pays for 6.2500 6.2500
grams that fill the store 100.0000 100.0000
steps one fan walks 432 432
The right-hand column of that table is the claim the whole chapter rests on, and the
comparison behind the word yes is not a subtraction against a tolerance. It
takes the two float64 values apart into their sixty-four bits and asks whether they are
the same sixty-four bits. Floating-point multiplication is allowed to round, but not
here: multiplying any float64 by exactly 1.0 is required to hand that float64 straight
back, so the identity genome is exact by the rules of the arithmetic and not by luck with
the decimals. The four numbers underneath are the row's own arithmetic, unchanged
because the row is unchanged.
Now move one factor and watch what a single number drags with it. A body twice the size
costs twice as much to stand up, so the ticks a full store covers halve and the ticks one
bite pays for halve with them. The store itself does not move, because the store is
Full and Full is its own gene: this world lets a creature be
heavy and badly banked at the same time, which is a thing real animals manage too.
$ go run ./cmd/stamp -mode stamp -body Bulk=2.0
stamp: the founding row with Bulk=2.0
i gene factor the row stamped the same bits
0 Bulk 2.0000 40.0000 80.0000 no
1 Full 1.0000 400.0000 400.0000 yes
2 Basal 1.0000 0.0040 0.0040 yes
3 Work 1.0000 0.6000 0.6000 yes
4 Bite 1.0000 0.2500 0.2500 yes
5 Convert 1.0000 4.0000 4.0000 yes
6 Reach 1.0000 1.0000 1.0000 yes
7 Sight 1.0000 12.0000 12.0000 yes
8 Top 1.0000 0.4500 0.4500 yes
9 Swing 1.0000 0.0833 0.0833 yes
9 of 10 stamped to the bit
what the row works out the row stamped
the bill, energy a tick 0.1600 0.3200
ticks a full store covers 2500.0000 1250.0000
ticks one bite pays for 6.2500 3.1250
grams that fill the store 100.0000 100.0000
steps one fan walks 432 432
One more, because it shows the top of the sight leash and a gene that cuts the other
way. Sight is asked for 2.00, the most it may hold, and Convert
is halved, so a gram of eaten plant is worth two energy units instead of four.
$ go run ./cmd/stamp -mode stamp -body Sight=2.0,Convert=0.5
stamp: the founding row with Sight=2.0,Convert=0.5
i gene factor the row stamped the same bits
0 Bulk 1.0000 40.0000 40.0000 yes
1 Full 1.0000 400.0000 400.0000 yes
2 Basal 1.0000 0.0040 0.0040 yes
3 Work 1.0000 0.6000 0.6000 yes
4 Bite 1.0000 0.2500 0.2500 yes
5 Convert 0.5000 4.0000 2.0000 no
6 Reach 1.0000 1.0000 1.0000 yes
7 Sight 2.0000 12.0000 24.0000 no
8 Top 1.0000 0.4500 0.4500 yes
9 Swing 1.0000 0.0833 0.0833 yes
8 of 10 stamped to the bit
what the row works out the row stamped
the bill, energy a tick 0.1600 0.1600
ticks a full store covers 2500.0000 2500.0000
ticks one bite pays for 6.2500 3.1250
grams that fill the store 100.0000 200.0000
steps one fan walks 432 864
Sight of twenty-four cells is useful in a valley twelve cells across, and it costs
864 samples a tick instead of 432. The bill did not move, so the machine pays for those
eyes and the creature does not, which is a hole this valley does not yet have a way of
charging for. The halved Convert is the cleaner trade: a bite still takes a
quarter of a gram off the plant, and it is now worth 0.50 energy units instead of 1.00, so
one bite pays for 3.1250 ticks instead of 6.2500 and it takes 200 grams of standing tissue
to fill a store that used to take 100. Same mouth, half the digestion, twice the eating.
Five properties of this package are the kind that break quietly, so they are tests and not paragraphs. Every one of them is a sentence somebody could get wrong in a way no compiler would notice.
$ go test ./internal/gene/ -run 'AnIdentityGenome|StampingLeaves|NoGeneCanBe|ACopyShares|TheFlatWalk' -v
=== RUN TestAnIdentityGenomeStampsTheFoundingRow
--- PASS: TestAnIdentityGenomeStampsTheFoundingRow (0.00s)
=== RUN TestStampingLeavesTheFoundingRowAlone
--- PASS: TestStampingLeavesTheFoundingRowAlone (0.00s)
=== RUN TestNoGeneCanBeWrittenOutsideItsRange
--- PASS: TestNoGeneCanBeWrittenOutsideItsRange (0.00s)
=== RUN TestACopySharesNothingWithItsOriginal
--- PASS: TestACopySharesNothingWithItsOriginal (0.00s)
=== RUN TestTheFlatWalkCoversEveryBlockExactlyOnce
--- PASS: TestTheFlatWalkCoversEveryBlockExactlyOnce (0.00s)
PASS
ok theworld/internal/gene 0.00s
The third of those walks all 395 genes and writes a huge negative and then a huge positive into each one, checking that what comes back is the two ends of that gene's own range: the clamp is tested per gene and not per range, so a gene that was quietly given the wrong span is caught by position. The fourth exists because the MIND block is a slice. Copying the struct alone copies the slice header and not the numbers, which would hand two genomes one set of weights and make every change to either a change to both. That mistake produces no error and no warning, and the first sign of it is a lineage where every creature has the same brain for reasons nobody can find.
The one seam the creature package is allowed
The terrarium learned this trick first. Package terra gained exactly one field
when creatures were added to the world, a function the valley calls once a tick, and
everything a creature does to a valley goes through it. The valley does not import the
creature package, does not know what a creature is, and its thousand-year digests did not
move when the field was added, because a function nobody has registered anything into costs
the run nothing at all.
Breeding needs the same kind of seam, and it needs exactly one. The roster is the thing that owns the list of living creatures and the only thing allowed to add to it or take from it, so the seam goes there.
// internal/beast/roster.go — inside type Roster struct
// Born is handed the valley once a phase, after the dead have been
// buried and before the phase hands anything back. It is the seam a
// creature that did not exist when the phase opened is put on the
// roster through, and it is at the end of the phase because that is
// the only place a newcomer is safe: everything alive has already
// had its tick, the index they all read is finished with, and the
// next phase builds its view after the newcomer is on the list.
//
// Nothing in this package registers anything into it. A roster with
// nothing registered runs the run it always ran, which is the only
// promise this field makes.
Born func(*terra.Valley)
// internal/beast/roster.go — the closing lines of Roster.Phase
if !r.Hasty && len(died) > 0 {
r.bury(v, died)
}
if r.Born != nil {
r.Born(v)
}
return died, refused
}
The position of those three lines is the whole design. A creature phase builds one reading of the world, casts every fan against it, steps the roster in order, and buries whatever stopped standing; the reading is built before the first animal moves and every animal in the phase shares it. A creature added in the middle of that would be a creature the reading does not contain, and the reading is indexed by cell, so the first animal stepped after it would ask the index about a cell and get an answer describing a valley that no longer exists. Added at the end, after the burial, the newcomer is on the list before anything reads the list again and is never stepped on the tick it appears. That is the same argument the burial itself is placed on, one line higher up.
Two properties of that field are load-bearing and both are cheap to state. It takes the
valley as an argument instead of closing over one, because a roster can be phased against
more than one valley and a closure that captured the wrong one would be a silent wrong
answer. And it hands nothing back, because whatever registers into it owns the roster
already and can add to it directly. The rest of internal/beast is untouched by
this volume: the row, the creature, the sensor row, the action table, the view, the ranking
walk and the phase order are all exactly what they were, and the package still draws no
random number anywhere. Every draw this volume makes is in internal/gene.
The herd that has to come back unchanged
Here is the test. Take the seed the creature chapters have been founding on. Stamp every
creature out of an identity genome instead of out of the row directly. Leave
Born unregistered. If a single number moves, the genome is not a wrapper round
the row, and the animals the last run left walking are not the animals this run is talking
about.
Start with the ground, because the ground is the half that has nothing to do with creatures. Streams 8 to 11 belong to the terrarium: seed flight, germination, plant death and where a founding generation lands. A valley with an empty creature phase registered into all three million six hundred thousand ticks of a thousand years has to close on the same 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
That run takes several minutes and prints three lines, which is the correct ratio for a check of this kind. 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 it catches a valley that arrived at the right answer by a different road. Both are the terrarium's own figures, and they were taken in a module whose roster now carries a breeding seam that is asked whether it is nil once every tick.
The other half is the animals. Nineteen creatures founded on stream 12, a brain apiece off stream 14, two years of valley, and the deaths written down as they happen.
$ 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
Nineteen founded out of twenty-four draws, because a draw that lands on a cell somebody is already standing on is spent and stands nobody up. Seventeen struck off and two still walking after six thousand seven hundred and fifty ticks. The first store ran out on tick 1044 and the animal it belonged to fell on cell 9,5; the last of the seventeen went on tick 3400 on cell 11,5. The run opened on tick 901 and covered 6,750 of them, so it closed on tick 7651, and nothing at all died in the 4,251 ticks between that last death and the end. The grams close in and out at 46567.944597, and the residue is about a hundredth of a nanogram, which is what tens of thousands of additions taken in two different orders cost.
The right-hand column is the point. Those are not figures a person typed in after reading the left-hand column; they are written into the bench as constants, and the word at the end of each line is a comparison the program made. A run at a different herd size or a different number of years prints its own numbers with nothing beside them, because a one-year herd of twelve is not a worse two-year herd of nineteen and there is no figure written down for it.
One more, at the size the phase was measured at rather than the size the pages use. Three thousand creatures on a hundred and fifty by a hundred cells of ground, a hundred and twenty ticks, all of them stamped, folded into one digest over the ground, every standing plant and every creature's place, heading, store and grams eaten.
$ go run ./cmd/stamp -mode crowd
stamp: 150x100 valley, 15000 cells, 591 plants standing, 3000 creatures
every one of them stamped from an identity genome
beast.Roster.Born is nil: nothing has registered a breeding pass into it
ticks run 120
creature-ticks handed out 352595
creatures struck off 269
creatures still walking 2731
digest of valley and roster e7098b55b7ce0cef unmoved
Three thousand animals handed out 352,595 creature-ticks over 120 ticks instead of the 360,000 a population that never died would have had, because 269 of them ran their stores down and stopped being stepped. Sixteen characters at the end, and they are the same sixteen the phase closed on before there was a genome in the world.
The reasonable next thought, once a creature can carry its own ten factors, is to give the founders some. A valley whose nineteen animals all have the identical body is a valley with nothing to select between, and drawing each founder ten numbers is four lines of code. The stream is right there: stream 12 is the creature stream, the founding already draws on it, and ten more draws a founder seems like nothing.
// cmd/stamp/main.go — inside found, with -drawn set
var body [gene.Bodies]float64
if drawn {
for j := range body {
body[j] = herd.Float64()
}
}
c := ground[herd.IntN(len(ground))]
if taken[c] {
continue
}
$ go run ./cmd/stamp -mode herd -drawn
stamp: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams
22 creatures founded on stream 12, each stamped from an identity
genome and given a brain of its own off stream 14
their ten body factors were drawn off stream 12 before they were placed
beast.Roster.Born is nil: nothing has registered a breeding pass into it
this run before the genome holds
creatures founded 22 19 NO
creatures struck off 21 17 NO
still walking after 6750 ticks 1 2 NO
first death tick 913 on 3,2 tick 1044 on 9,5 NO
last death tick 3214 on 11,3 tick 3400 on 11,5 NO
Every line says NO, and the first one is the one to read twice. Twenty-two creatures were founded where nineteen were founded before, out of the same twenty-four attempts and the same seed. Nothing in that block touches how many animals there are. What they touch is the position in the stream that the placement draw is taken from.
A seeded generator is a fixed sequence of numbers, and a draw is not a request for a random value: it is a request for the next value. The founding takes one draw per attempt to pick a cell. Spend ten draws on a body before each of those, and attempt number seven now reads the seventy-seventh number in the sequence instead of the seventh. Different cells come out, so a different number of attempts collide with a cell already taken, so the population is a different size. Everything after that follows: different animals in different places, so different plants get eaten, so different stores run out on different ticks. First death on tick 913 instead of 1044, last death on tick 3214 instead of 3400.
The rule the mistake teaches is narrow and hard. Every stream in this world belongs to exactly one job, and how many numbers that job takes is part of the job. Adding a draw to an existing stream renumbers every draw that comes after it. The fix is not to draw fewer numbers or to draw them later; the fix is that a founder's ten body factors are all 1.00 and are not drawn at all, which is what the identity founding does, and the variation this volume is about has to come from somewhere no existing stream is looking.
Why genotype stamps phenotype
Strip the valley out and what is left is a pattern that shows up everywhere something is built from a specification. On one side sits a description: compact, unitless, made of numbers that mean nothing on their own. On the other sits the thing the description makes: full of units, expensive, tangled up with the world it stands in. Between them is one function that reads the description and writes the thing, and the function runs in exactly one direction.
The direction is the load-bearing part. Stamp takes a row by value and hands a
row back by value, so the founding row cannot be edited by anything that stamps from it,
and a hundred creatures stamped in a row all come off the same untouched original.
Net copies the MIND block into a network instead of pointing the network at
it. A creature's store, its cell, its heading, its identity, the tick it was born on and
every total it ran up in its life live on the body, and there is no method anywhere that
takes any of them and writes into a genome. That gap is deliberate, and it marks the
difference between a world where children inherit what their parents were born with and a
world where they inherit what their parents did with it. Only the first of those behaves
like anything alive.
The other half of why this works is that the description is smaller than the thing and uniform where the thing is not. Ten factors, 378 weights, three genes nothing reads yet and four that decide what a creature looks like: 395 numbers, every one of them a bare quantity with a range and no units. An operator that wants to copy one of these imperfectly can treat all 395 identically. It does not need to know that gene 0 is measured in grams and gene 7 in cells, and it does not need to know that genes 10 through 387 are read as a matrix by something that multiplies them. That uniformity is bought with one decision, made on the first page of the package: the gene multiplies rather than replaces.
What it costs is a layer of indirection nobody can see in a debugger. A creature whose
Bulk reads 80 grams has 2.00 written in it, and finding out why an animal is
heavy means reading a factor and remembering the founding row. That is a real price, and
the three runs above are what pay it back: the founding row is still the single
place in this world where a gram is priced against a bite, and a genome of all ones still
reproduces the valley exactly, down to the tick each of seventeen animals stopped.
- You can say why the identity genome stamps the founding row to the bit, and name the property of floating-point multiplication that makes it exact instead of close.
- Handed a body factor and a founding value, You can work out the stamped number, the bill it produces, and the ticks a full store then covers.
- You can explain why
Sightgets half the range the other nine body genes get, and give the sample count at both ends of it. - You can say where the four blocks start in the flat walk, how many numbers the walk covers, and why only four methods anywhere are allowed to answer that.
- You can point at the one field this volume adds to
internal/beast, say which line of the phase calls it and why it is that line and not an earlier one. - Shown a run where a founding spends extra draws on an existing stream, You can predict that the population size changes and explain the mechanism without running it.
Exercise 1 — the largest body allowed. Work out the bill, the
ticks a full store covers, the ticks one bite pays for and the samples one fan walks
for a creature whose Bulk factor is 4.00 and whose other nine are 1.00.
Then check with go run ./cmd/stamp -mode stamp -body Bulk=4.0.
Bulk stamps to 160 grams. The bill is bulk times basal, so 160 × 0.004 = 0.6400
energy units a tick, four times the founding row's 0.1600. The store is untouched at
400, so the ticks it covers are 400 ÷ 0.64 = 625.0000, a quarter of 2,500. One
bite is still a quarter of a gram at four units a gram, so still 1.00 unit, and
1.00 ÷ 0.64 = 1.5625 ticks. The samples do not move at all: they depend on
Sight and on nothing else, so they stay at 432. The run agrees on all
five.
The interesting number is the last one. A creature four times the mass sees exactly as far and costs the machine exactly as much to look, while costing itself four times as much to exist. Mass is expensive to the animal and free to the clock; sight is the other way round.
Exercise 2 — asking for more than the range allows. Predict
what comes out of go run ./cmd/stamp -mode stamp -body Sight=8,Bulk=0.1,
including the samples and the bill, then run it and say which line of the package
decided the answer.
Neither number arrives. Sight is asked for 8.00 and the printed factor
is 2.00; Bulk is asked for 0.10 and the printed factor is 0.2500. The
stamped row is 24 cells of sight and 10 grams of body, so the fan walks
9 × 24 ÷ 0.25 = 864 samples and the bill is 10 × 0.004 = 0.0400
energy units a tick. A store of 400 against a bill of 0.04 covers 10,000 ticks, which
is most of three years of standing perfectly still.
The line that decided it is the first line of Genome.Put, which runs
g.Span(i).Clamp(x) before it writes anything. The command line asked for
8 and the genome never held 8 for an instant. That is the difference between clamping
on the way in and clamping on the way out: a genome that stored 8.00 and clamped when
it was read would print 8.00 in every record of that creature ever written, and every
one of those records would be a lie about an animal that could only ever see 24
cells.
Exercise 3 — design a creature that starves in five hundred ticks. Find two body factors that give a creature exactly 500 ticks of standing still on a full store, leaving the other eight at 1.00. Then stamp it and check the third line of the table.
The ticks a full store covers are the store divided by the bill, which is
(400 × fFull) ÷ (40 × fBulk × 0.004
× fBasal), and that is 2,500 × fFull ÷
(fBulk × fBasal). Wanting 500 means wanting that quotient
to be a fifth. One pair that does it is a quarter of the store against a quarter more
upkeep a gram: go run ./cmd/stamp -mode stamp -body Full=0.25,Basal=1.25
prints a bill of 0.2000 and 500.0000 ticks. Halving the store against a basal rate
two and a half times as dear does it too, and so would leaving the store alone and asking
Bulk for 5.00, except that 5.00 is outside the range and clamps to 4.00,
which lands on 625 instead.
Look at what else moved on that run. The grams that fill the store fell from 100 to 25, because a quarter of a store takes a quarter of the tissue to fill, and the ticks one bite pays for fell from 6.2500 to 5.0000. Four of the five lines moved off two factors, which is the thing to carry out of this page: the ten numbers are not ten independent knobs, and every one of them is wired into two or three of the quantities a creature actually lives or dies by.
Every genome in this valley is still the same genome. Copying one exactly is a solved problem and a useless one, so the question the package now has to answer is what a copy is allowed to get wrong: which of the 395 numbers moves, how far it may go in one step, and how many numbers the copy takes off a stream whether anything moves or not. That last part is the one the run above has already shown teeth about. A copy whose draw count depends on what the draws said is a copy that puts a different number of steps into the sequence every time it is made, and a world where that happens is a world that cannot be replayed.