A Species Is a Row of Numbers
The species block
Every plant in the terrarium has used the same physiology so far: the same light share, thirst, hunger, upkeep and root reach. The drawing side has had the same problem, with grammar settings typed on the command line instead of belonging to a kind of plant. A species is one row of parameters read by the budget and the turtle; changing the row changes the plant, while the ledger, bed, light grid and drawing code stay the same.
Four rows make four claims about the same valley. Moss spends little and spreads low. Herb races through one season. Scrub survives dry ground. Tree buys height and pays for it in slow growth.
The row is laid out now the way a genome can hold it later, but it is not a genome here. No mutation, crossing or selection exists on this page. The point is smaller: the code reads numbers from one block instead of from scattered constants.
The fourteen-number block
The honest way to write the block is to go through the code and collect every number something already reads. On the drawing side there are four: which rule set in the grammar library, the branch turn written as one part in so many of a whole turn, how many world pixels one stroke covers, and a three-number price list saying what one stroke weighs, how many strokes a generation multiplies by, and the generation the grammar is never grown past.
On the living side there are nine. The grams a seed germinates at. The share of its cell's daylight the crown intercepts, the water spent per unit of energy fixed, the nutrient spent per unit fixed, and the upkeep charged on every gram already standing. How far the roots reach. How far the crown reaches, how much leaf a gram carries, and how much height a gram stands. That is the whole list, and the list is closed: a number that no method reads is a number that cannot make one plant differ from another.
// internal/terra/species.go
// Species is one kind of plant written down as numbers. The first half
// is the form: which grammar draws it, how far a branch swings, how
// long one stroke is, and what each generation of that grammar costs in
// grams. The second half is the physiology: what a gram earns, what it
// drinks and eats to earn it, what it costs to keep, and how far the
// roots and the crown reach. There is nothing else. Two plants of
// different kinds run the same code and differ only here.
type Species struct {
Name string
// the form
Grammar string // the rule set in grow.Named this plant grows by
Part float64 // a branch turns one part in this many of a turn
Seg float64 // world pixels one drawn stroke covers
Form Form // grams generation n of that grammar needs standing
// the physiology
Seed float64 // grams it germinates at
Catch float64 // the share of its cell's light the crown intercepts
Thirst float64 // water units spent per energy unit fixed
Hunger float64 // nutrient units spent per energy unit fixed
Upkeep float64 // energy one gram of standing tissue costs a tick
Root float64 // how far the roots reach, in cells
Reach float64 // how far the crown reaches, in cells
Leafy float64 // cells of leaf area carried per gram
Tall float64 // cells of height per gram
}
// Physiology is the budget half of the row, handed to the ledger the
// first three chapters of this volume wrote.
func (s Species) Physiology() Plant {
return Plant{Mass: s.Seed, Catch: s.Catch, Thirst: s.Thirst,
Hunger: s.Hunger, Upkeep: s.Upkeep, Root: s.Root}
}
// Canopy is the crown half of the row, handed to the light field.
func (s Species) Canopy() Canopy {
return Canopy{Crown: s.Reach, Leafy: s.Leafy, Tall: s.Tall}
}
// Germinate turns a row of numbers into one plant standing somewhere.
// Every trait is copied into the individual and the row is never
// consulted for a rate again, so nothing a plant does can reach back
// and edit the kind it came from.
func (s Species) Germinate(at sim.Coord, t int) *Stand {
kind := s
return &Stand{Name: s.Name, At: at, Kind: &kind,
Plant: s.Physiology(), Crown: s.Canopy(), Born: t, Sown: -1}
}
// Draft is the form half of the row, handed to the grower chapter 33
// wrote, grown to whatever generation this much tissue can pay for. The
// picture and the budget meet in exactly one place: the generation
// number, which is a price.
func (s Species) Draft(mass float64, seed uint64) grow.Plant {
g := grow.Named[s.Grammar]
return grow.Plant{Axiom: g.Axiom, Rules: g.Rules, Gen: s.Form.Stage(mass),
Seg: s.Seg, Turn: field.Turn / s.Part, Up: field.Turn / 4, Seed: seed}
}
Four methods, and not one of them invents a number: every value the four hand out came
off the row. Physiology and Canopy hand slices of it to
machinery that already existed and was already taking those exact fields, so neither of
them is a conversion so much as a re-labelling. Germinate copies twice. The
budget and the crown are stamped into the individual as plain values, and the row itself
is copied into a local variable the new stand then points at, so what a plant carries is
its own private duplicate of the table line it came from. Two plants of one species are
two independent sets of numbers that started out agreeing, and neither of them can reach
the line in Flora they were stamped from.
What Germinate hands back is wider than the stand the shading chapter
published. That chapter had no species for a plant to point at, so a plant there was a
position, a budget, a crown, the water under it and the light over it, and nothing on
it could say what kind of thing was standing there. A filled-in row makes that sayable,
and it also gives an individual a few facts a kind cannot have. Here is the type as
this page leaves it.
// internal/terra/sky.go
// Stand is one plant standing somewhere: the budget of the physiology
// chapters, the crown it carries, the row it was stamped from, and the
// light that reached the top of that crown this tick.
type Stand struct {
Name string
At sim.Coord
Kind *Species
Plant Plant
Crown Canopy
Water float64 // water units the ground under it can give up in a tick
Lit float64 // light reaching the top of its crown, set by Cast
Born int // the tick it germinated on
Sown int // the year it last spent mass on seed, or -1
Last Ledger
}
The five new fields are all of one sort: what this plant knows that its row does not.
Name is the row's name copied down, so a stand can be printed or counted
on its own, without whoever is counting holding the table open beside it.
Kind is the pointer to the private duplicate, which lets a plant be asked
what it is and still leaves it no way to edit the line in Flora it came
from: the copy belongs to the plant, the table belongs to the valley. Born
is the tick it germinated on, which is a fact no row could ever hold, because a row is
a kind and a kind has no birthday. Sown is the year it last spent mass on
seed. It reads −1 for never, and it reads −1 on every plant on every tick
of this page, since a valley whose sky never changes has no years in it and nothing
here has anywhere to put a seed.
Last is the ledger line this plant spent on the previous tick, kept on
the plant instead of being handed out by whatever is running the loop. Every tick loop
below writes st.Last = l as it goes, and that one line is where the
limit column comes from in the tables that print where a run finished up:
two thousand ticks go by with nothing printed, and then the plant is asked what stopped
it last. The comment over the type moved with the fields. It used to name the ground a
plant is rooted in as the third thing a stand holds, and the row it was stamped from is
the better answer now that there is a row to point at.
Draft is where the two halves of the block finally touch, and they touch at
one number. The grammar knows nothing about energy. The ledger knows nothing about
strokes. Between them sits a price list, and the only thing the budget hands the drawing
is the mass it managed to stand up; the drawing hands back the largest generation that
mass can pay for. Change any physiology number and the picture changes, without a single
line of drawing code hearing about it.
Four species rows
Now fill four of them in. A strategy is a bet, so say what each row is betting before reading a single number off it.
The moss bets on leftovers. It is cheap to keep and it catches almost nothing, so it can never be big, and the price list of its grammar is priced in tenths of a gram so it does not need to be. The herb bets on open ground and on getting there first: it catches more of its cell's daylight than anything else in the table and pays for that with expensive tissue, which caps it low and gets it there fast. The scrub bets on ground the others cannot pay for. It spends a quarter of a water unit where the herb spends nine tenths, and it spreads its roots over three cells' radius so that no single cell is asked for much. The tree bets on time. Its tissue is the cheapest in the valley to keep, which makes it the slowest thing here and the largest, and everything it earns goes into height nothing else can reach.
// internal/terra/species.go
// Flora is the valley's species list: four rows, four strategies, one
// set of machinery. Adding a fifth kind of plant to this world is
// adding a line to this table.
var Flora = []Species{
{
Name: "moss", Grammar: "fan", Part: 5, Seg: 1,
Form: Form{SegMass: 0.10, Fan: 4, Max: 2},
Seed: 0.25,
Catch: 0.06, Thirst: 0.30, Hunger: 0.04, Upkeep: 0.020, Root: 1.0,
Reach: 0.5, Leafy: 0.020, Tall: 0.002,
},
{
Name: "herb", Grammar: "sprig", Part: 8, Seg: 2,
Form: Form{SegMass: 2, Fan: 5, Max: 3},
Seed: 1.00,
Catch: 0.30, Thirst: 0.90, Hunger: 0.20, Upkeep: 0.050, Root: 1.5,
Reach: 1.5, Leafy: 0.050, Tall: 0.022,
},
{
Name: "scrub", Grammar: "fan", Part: 12, Seg: 2.5,
Form: Form{SegMass: 2.5, Fan: 4, Max: 3},
Seed: 0.50,
Catch: 0.12, Thirst: 0.25, Hunger: 0.06, Upkeep: 0.008, Root: 3.0,
Reach: 2.5, Leafy: 0.025, Tall: 0.012,
},
{
Name: "tree", Grammar: "sprig", Part: 14, Seg: 3,
Form: Form{SegMass: 4, Fan: 5, Max: 4},
Seed: 2.00,
Catch: 0.25, Thirst: 0.60, Hunger: 0.15, Upkeep: 0.005, Root: 2.5,
Reach: 2.5, Leafy: 0.030, Tall: 0.030,
},
}
$ go run ./cmd/flora -mode row
flora: 4 rows, and what each one predicts before a tick is taken
12.00 of daylight on an open cell, a plant at 4,1
grammar part seg gen 0 gen 1 gen 2 gen 3
moss fan 5 1.0 0.10 0.40 1.60 6.40
herb sprig 8 2.0 2.00 10.00 50.00 250.00
scrub fan 12 2.5 2.50 10.00 40.00 160.00
tree sprig 14 3.0 4.00 20.00 100.00 500.00
seed catch thirst hunger upkeep root crown tall
moss 0.25 0.06 0.30 0.04 0.020 1.0 0.5 0.002
herb 1.00 0.30 0.90 0.20 0.050 1.5 1.5 0.022
scrub 0.50 0.12 0.25 0.06 0.008 3.0 2.5 0.012
tree 2.00 0.25 0.60 0.15 0.005 2.5 2.5 0.030
soil income ceiling gen tall pass alive at branches at
moss 4 0.7200 36.00 2 0.07 0.2800 0.0833 0.1333
herb 6 3.6000 72.00 2 1.58 0.6000 0.1667 1.6667
scrub 12 1.4400 180.00 3 2.16 0.7857 0.0333 0.6667
tree 10 3.0000 600.00 3 18.00 0.1429 0.0400 0.4000
Two of the four grow by the same rule set and nobody would put them in the same species.
The moss and the scrub both grow by fan, one at a fifth of a turn and one
stroke to the pixel, the other at a twelfth of a turn and two and a half pixels a stroke,
and the price of a generation differs between them by a factor of twenty-five. Swapping a
rule set is the biggest lever in the block, and it is not the only one that changes what
a plant looks like.
The third table is where the bets turn into arithmetic, and not one figure in it came
from a run. The soil column is how many cells of real ground the root radius
finds from that cell, so the scrub's radius of 3 reaches twelve where the moss's radius
of 1 reaches four. The income is the first half of the budget chapter's arithmetic:
the light landing multiplied by the crown's share, or as much of that as the ground can
pay for. The ceiling is that income divided by upkeep. Read the ceiling column against
the price list above it: the tree clears the 500 grams generation 3 costs with a hundred
to spare, and the generation after that wants five times 500, which nothing in this
valley is ever going to earn.
Two columns on the right are new, and they are the numbers that decide whether a row can live somewhere.
Numbers first. A moss seed germinates holding 0.25 grams. Its upkeep on that is 0.020 × 0.25 = 0.005 energy units a tick, and it has to earn at least that much or it starts burning tissue on its first tick. It earns 0.06 of whatever light reaches it, so the light it needs is 0.005 ÷ 0.06 = 0.0833 units. Below that figure a moss seed is smaller on tick 2 than it was on tick 1, and it never recovers.
Write the upkeep per gram u, the seed's mass in grams s, and the crown's share of the light c. The light a seed needs to hold its own weight is the same three numbers in the same order:
alive = u · s ÷ c
0.020 × 0.25 ÷ 0.06 = 0.0833, which is the moss's entry in the
alive at column, and 0.005 × 2.00 ÷ 0.25 = 0.0400 is the
tree's. Being alive is a low bar. The second figure asks something harder: what light
does a plant need before it can afford the first branching of its own grammar. That is
the same division with the price of generation 1 standing in for the seed. Call it g:
branches = u · g ÷ c
For the moss, 0.020 × 0.40 ÷ 0.06 = 0.1333. For the herb, 0.050 × 10.00 ÷ 0.30 = 1.6667, more than twelve times as much light for the same achievement. The two figures together say something the ceiling alone cannot. A plant can clear the first bar and fail the second for its whole life, alive and never once anything but a single stroke of stalk, and the gap between a row's two bars is decided as much by the price of its grammar as by anything in its physiology.
$ go run ./cmd/flora -mode alone -only scrub -ticks 600 -every 100
flora: one plant of each kind, each alone in its own 12x8 valley at 4,1
12.00 of daylight, the ground putting back 0.60 of moisture and 0.150 of nutrient a cell a tick
scrub, 600 ticks
tick mass caught grown ceiling gen segments limit
1 1.9360 1.4400 1.4360 180.00 0 1 light
2 3.3605 1.4400 1.4245 180.00 0 1 light
3 4.7736 1.4400 1.4131 180.00 0 1 light
100 99.6045 1.4400 0.6484 180.00 2 16 light
200 143.9920 1.4400 0.2904 180.00 2 16 light
300 163.8725 1.4400 0.1301 180.00 3 64 light
400 172.7767 1.4400 0.0583 180.00 3 64 light
500 176.7648 1.4400 0.0261 180.00 3 64 light
600 178.5510 1.4400 0.0117 180.00 3 64 light
The two right-hand columns are the block's two halves keeping step. The scrub is a single stroke until it has 10 grams standing, four strokes after that, sixteen once it clears 40 grams, and sixty-four when it passes 160 somewhere between tick 200 and tick 300. The ceiling column never moves, because the ground under this plant is being refilled faster than one scrub can drink it, so the only thing holding it back is the light, exactly as the paper table said. Nobody typed a generation anywhere in this run.
The trickle is stated on the second line because it is a decision, not a fact of the world. This page holds the ground generous on purpose so that four rows can be compared without the soil deciding the argument, the way the shading chapter held the water flat so that only height varied. The next stage turns it down and the argument changes completely.
$ go run ./cmd/flora -mode alone -ticks 2000
flora: one plant of each kind, each alone in its own 12x8 valley at 4,1
12.00 of daylight, the ground putting back 0.60 of moisture and 0.150 of nutrient a cell a tick
mass height leaf gen segments limit world
moss 36.0000 0.0720 0.7200 2 16 light 05f1078fa6186607
herb 72.0000 1.5840 3.6000 2 25 light 9f20a8ae37b2020c
scrub 180.0000 2.1600 4.5000 3 64 light 7c7adbc0c7bf6090
tree 599.9735 17.9992 17.9992 3 125 light 0504e58f74b3fb24
36.0000, 72.0000, 180.0000 and 599.9735, against a paper table that predicted 36, 72, 180 and 600 before anything ran. Only the tree misses, by 0.0265 of a gram, and it misses for the reason the budget chapter gave: a plant closes the same fraction of its remaining distance every tick and never quite arrives. That fraction is the upkeep, and the tree's is the smallest in the table at five thousandths, so it is the one row that two thousand ticks is not long enough to finish.
The turtle from the row
The drawing side has been able to put a plant on a frame since the grammar chapter, and
it has never been told what a species is. It does not have to be. A row supplies the rule
set, the turn, the stroke length and, through the price list, the generation; the turtle
takes those and hands back segments; the line routine paints them. All of it is one
function with no switch in it, and the bench calls that one function four
times.
// cmd/flora/shot.go
// sprout grows one row of the species table at one mass and paints it
// into a buffer at one column. Nothing in here asks which species it is
// holding: the row is handed to the grower chapter 33 wrote, the turtle
// reads the string it produces, and the same three lines draw a moss
// and a canopy tree.
func sprout(b *render.Buffer, s terra.Species, mass float64,
x, base int, pseed uint64) (int, int, grow.Box) {
d := s.Draft(mass, pseed)
str, segs := d.Sprout(vec.Vec2{X: float64(x), Y: float64(base)})
if _, ok := grow.Balanced(str); !ok {
die(fmt.Errorf("%s at %.2f grams: the brackets do not close", s.Name, mass))
}
for _, sg := range segs {
draw(b, sg)
}
return d.Gen, len(segs), grow.Bounds(segs)
}
$ go run ./cmd/flora -mode shot -shot assets/frames/flora-four-rows.png
flora: 4 rows on the bare frame, out of row 110
grammar part seg grams gen segments w by h its own frame
moss fan 5 1.0 36.00 2 16 4 by 4 115643045c8a8fb1
herb sprig 8 2.0 72.00 2 25 9 by 18 95ee7b9effabe33a
scrub fan 12 2.5 180.00 3 64 14 by 20 66de1958cdd84ce4
tree sprig 14 3.0 600.00 3 125 27 by 81 877e7b705837ea3a
the four together 843f8622babfd8d836e08dde6f2e0f60288573d0a60c479779028a15bdafedc7
wrote assets/frames/flora-four-rows.png
Each row is drawn twice: once alone on an empty frame, which is where its own digest comes from, and once into the shared frame with the other three. Separating them is cheap and it buys a real thing. If a later edit changes what a moss looks like, exactly one of those five digests moves. If it changes the line routine or the colour ramp, all five move at once. A single hash over the finished picture cannot tell those two apart, and they call for opposite reactions.
The size column is the physiology showing through the drawing. Four pixels by four for the moss and twenty-seven by eighty-one for the tree, from one turtle, because 36 grams buys 16 strokes of one pixel and 600 grams buys 125 strokes of three.
assets/frames/flora-four-rows.png: the moss, the herb, the scrub and the
tree, each at the mass its own row settles at, drawn by the same turtle in the same three
greens. The size difference is 0.020 of upkeep against 0.005, and not a drawing decision.
The tradeoff table
Four rows that each do well on good ground in full sun say nothing. A strategy is a claim about bad conditions, so put them in some.
$ go run ./cmd/flora -mode under
flora: a seedling under grown trees, each standing at 600.00 grams
one crown spreads 18.00 cells of leaf over 21 cells, letting 0.1429 of the light through
crowns light moss herb scrub tree
ceiling gen segs ceiling gen segs ceiling gen segs ceiling gen segs
0 12.0000 36.000 2 16 72.000 2 25 180.000 3 64 600.000 3 125
1 1.7143 5.143 2 16 10.286 1 5 25.714 1 4 85.714 1 5
2 0.2449 0.735 1 4 1.469 0 1 3.673 0 1 12.245 0 1
3 0.0350 0.105 0 1 0.210 0 1 0.525 0 1 1.749 0 1
One crown overhead costs every row about six sevenths of its ceiling, and the moss is the
only one that loses nothing at all from its picture: still generation 2, still sixteen
strokes, at a seventh of the mass. Its grammar was priced for a plant that was never
going to be big. Under two crowns the moss is the only one of the four that is still a
plant at all; the other three are single strokes with a ceiling above their seed mass,
alive and drawing one line. Under three crowns the answer changes hands. Compare each
ceiling in that last row against the seed masses in the table above: 0.105 against the
moss's 0.25, 0.210 against the herb's 1.00, 1.749 against the tree's 2.00, and 0.525
against the scrub's 0.50. Three of the four are already burning tissue on their first
tick. The scrub clears it by twenty-five thousandths of a gram, which is the
alive at column being right: 0.0333 units of light, the lowest bar in the
valley, and there is 0.0350 available.
$ go run ./cmd/flora -mode alone -ticks 2000 -rain 0.05 -feed 0.01
flora: one plant of each kind, each alone in its own 12x8 valley at 4,1
12.00 of daylight, the ground putting back 0.05 of moisture and 0.010 of nutrient a cell a tick
mass height leaf gen segments limit world
moss 33.3333 0.0667 0.6667 2 16 water 18c2c548bb305741
herb 6.0000 0.1320 0.3000 0 1 nutrient dbe4382a74f3ddb2
scrub 180.0000 2.1600 4.5000 3 64 light 7c7adbc0c7bf6090
tree 133.3296 3.9999 3.9999 2 25 nutrient bdbd9464f3115df6
The trickle is down by a factor of twelve and fifteen and the scrub has not noticed. Its
digest is the same 7c7adbc0c7bf6090 as the generous run, to the last bit of
the last cell, because twelve cells refilling at 0.05 still pay for more energy than its
crown can catch, and a plant that is light-limited does not care what the ground has
spare. That is the drought bet paid in full, and it was bought with a crown that
intercepts 0.12 where the herb's takes 0.30.
The herb is the same bet from the losing side. It falls from 72 grams to 6, and 6 is
below the 10 grams its own first branching costs, so the fastest plant in the valley
spends two thousand ticks as one stroke of stalk. Its limit reads
nutrient, which is the interesting part: it is not the water that finished
it despite being the thirstiest row in the table, but the nutrient, because catching
three tenths of the daylight means eating 0.20 of nutrient for every unit of that
daylight it fixes, out of six cells. The tree comes down to 133.3296 grams, less than a
quarter of what it manages on good ground, by the same nutrient arithmetic on ten cells,
and it is still the second largest thing here.
$ go run ./cmd/flora -mode mixed -ticks 2000 -every 400
flora: one of each kind in one 12x8 valley, 2000 ticks
moss at 3,1 herb at 4,1 scrub at 6,1 tree at 5,1
tick moss herb scrub tree moss lit herb lit scrub lit tree lit
1 0.959 4.540 1.932 4.990 11.899 11.966 11.966 12.000
400 9.398 17.949 63.416 519.474 2.792 2.867 3.102 12.000
800 5.512 11.070 32.445 589.157 1.784 1.828 1.901 12.000
1200 4.947 10.119 26.687 598.540 1.642 1.684 1.739 12.000
1600 4.870 9.991 25.848 599.803 1.622 1.665 1.718 12.000
2000 4.860 9.974 25.732 599.974 1.620 1.662 1.715 12.000
moss 4.860 grams where alone it reaches 36.000, 0.010 cells tall, generation 2 in 16 strokes
herb 9.974 grams where alone it reaches 72.000, 0.219 cells tall, generation 0 in a single stroke
scrub 25.732 grams where alone it reaches 180.000, 0.309 cells tall, generation 1 in 4 strokes
tree 599.974 grams where alone it reaches 599.974, 17.999 cells tall, generation 3 in 125 strokes
world a56a7bce69588ab0
Read the tick 400 row and then the tick 2000 row. The other three all peak somewhere in the first four hundred ticks and then come back down, because the tree is still growing underneath them and the light over their heads keeps falling: 2.792 units at tick 400, 1.620 at tick 2000. Nothing kills them. They are carried down to whatever their own ceiling is under the light they end up with, and a ceiling a plant has already passed makes it shrink. The tree's own column reads 12.0000 on every printed row and it finishes on 599.974 grams, which is what it weighs standing alone in an empty valley, to three decimal places.
The moss is the one to look at hardest. It ends at 4.860 grams, a seventh of what it manages alone, and it is still generation 2 in sixteen strokes: the only one of the four that is the same plant in the shade as it is in the open. The herb, which was the fastest grower in the valley by a wide margin at tick 400, ends as a single stroke.
One warning before the failure below. The rows above were designed by moving numbers around until the four bets came out distinct, and nothing in the code checks a row for sense. A row with the tree's upkeep, the herb's catch and the scrub's thirst is legal, and the world would grow it happily into something better than everything else at everything. The trade-offs live in the author's head and in the comments, and not in a single line of Go.
The mixed run leaves an obvious job. The tree wins the light and its own seedlings would germinate into the shade underneath it, so the table needs a row that can establish itself down there: a sapling with a bigger seed to start on, a wider crown to gather what little light gets through, and more leaf on every gram to gather it with. Three edits, all of them things anybody would say about real trees without stopping to check, and every one of them is already a field in the block.
// cmd/flora/main.go
// sapling is the row this chapter gets wrong on purpose: a tree edited
// to survive in shade by every knob that sounds like it should help.
var sapling = terra.Species{
Name: "sapling", Grammar: "sprig", Part: 14, Seg: 3,
Form: terra.Form{SegMass: 4, Fan: 5, Max: 4},
Seed: 4.00,
Catch: 0.25, Thirst: 0.60, Hunger: 0.15, Upkeep: 0.005, Root: 2.5,
Reach: 3.5, Leafy: 0.060, Tall: 0.030,
}
$ go run ./cmd/flora -mode dim -ticks 400
flora: one plant of each row in 0.0500 units of light, nothing else standing
seed crown leafy income ceiling floor t=1 t=50 t=200 t=400
tree 2.00 2.5 0.030 0.0125 2.500 0.0400 2.0025 2.1108 2.3165 2.4327
sapling 4.00 3.5 0.060 0.0125 2.500 0.0800 3.9925 3.6675 3.0504 2.7020
in the open tree settles at 600.000 grams and its crown lets 0.1429 of the light through
in the open sapling settles at 600.000 grams and its crown lets 0.0270 of the light through
Stood in five hundredths of a unit of light, the plain tree grows and the shade specialist
shrinks, and they are heading for the same 2.500 grams from opposite directions. Four
hundred ticks in, the row that was edited for shade weighs 2.7020 grams, having germinated
at 4.00 and burned 1.2980 grams of tissue on the way down, while the row that was not
edited at all weighs 2.4327 and is still climbing. The income column
settles what happened: both rows earn 0.0125 units a tick, to the last digit.
Trace each edit to the code that reads it, using the figure at the top of the chapter.
The crown radius is read by Canopy.Spread and Canopy.Pass, both
of which are about the shade this plant throws on other plants. Leaf per gram is read by
the same two. Neither of them appears anywhere in Plant.Limit, so neither of
them can put a single unit of energy into this plant's own books. One number in the whole
block decides what a plant earns out of a given amount of light, and it is
Catch; one more decides how much of that earning stays standing, and it is
Upkeep. Both were left exactly as they were. The seed mass is worse than
useless here: it is read as the plant's starting mass, and upkeep is charged on mass, so
doubling the seed doubles the light the seedling needs before it stops shrinking. The
floor column says 0.0800 against the tree's 0.0400, and there is a whole
band of light between those two figures where the ordinary tree lives and the shade
specialist cannot.
The last two lines are the joke at the end. In the open, the two rows settle at the same 600 grams and the sapling's crown lets 0.0270 of the light through where the tree's lets 0.1429. The row that cannot live in shade throws the deepest shade in the table, passing a fifth as much light to whatever is standing under it.
The rule to carry out of this is not about plants. When behaviour is data, an edit is only as good as your knowledge of which code reads the field. Guessing from the field's name is how you get three careful edits that change nothing you wanted and one thing you did not. The check is mechanical and takes a minute: for each number you are about to move, find every method that reads it, and ask whether any of them is on the path to the outcome you are aiming at.
A parameter block is a cheap and strong model and it has one hard edge, which this book will meet again. A row can only vary numbers the machinery already reads, so a species can differ from another in degree and never in kind. There is no row that fixes its own nitrogen, because there is no term for that in the ledger. There is no row that stores water in a swollen stem and lives off it through a dry spell, because the budget has one mass and no reserve. There is no row that drops its leaves when the light goes, no row with a taproot that reaches something the surface cells do not have, and no row whose seed carries a food supply, which is what the failure above was actually reaching for. Every one of those is a new effect, and a new effect is an edit to the machinery every plant in the valley runs, not a line in a table.
That is the trade the block is making and it is a good trade at this size. Four species cost four lines and no branches anywhere; a fifth costs one more line. The day a real new organ is wanted, the cost is a term in the ledger and a field in the block, paid once, and every existing row keeps working with a zero in it. Knowing which of the two kinds of change you are making before you start is most of the skill.
Parameters as behaviour
Strip the plants out and the pattern is one of the oldest in programming. There is a
machine, written once, that reads its behaviour out of a record instead of having it
written into its branches. Adding a kind of thing adds a record. The machine does not
grow, does not learn a new case, and cannot be broken by the addition, because there is no
if anywhere in it that mentions any particular kind.
Three properties make it hold up, and all three are visible above. The first is that the
record is complete: every number that could make two individuals differ is in the block,
so there is no second place to look when a plant behaves oddly. The second is that the
record is copied on use. Germinate hands an individual its own numbers, so a
plant that later changes can change without every other plant of its kind changing with
it, and that property is the whole reason the block is useful to build rather than reading
the table directly at every tick. The third is that the record is inert. It holds no
pointers, no methods that surprise anybody, and no state, so it can be printed as a table,
compared field by field, and written down.
The paper table earns a second mention. Every important number about these four rows was computed from the row before a tick was taken, and the runs agreed: 36, 72, 180 and 600 predicted, 36.0000, 72.0000, 180.0000 and 599.9735 delivered. That discipline is what makes the failure above findable in one run instead of an afternoon. When the shade specialist came out with an income of 0.0125 units, the same figure as the row it was supposed to improve on, there was a number to compare it against and a short list of fields that could have moved it.
And a row of numbers is a thing that can be copied. Copy it exactly and the copy is the same species; copy it with a small mistake in one field and the copy is a plant that is nearly its parent, which is a sentence with a great deal of the rest of this book folded into it.
- Handed a species row, you can name which method reads each of its fourteen numbers, and say which of them can and cannot change what that plant earns in a tick.
- compute a row's open-ground ceiling, the generation its grammar can afford at that ceiling, and the two light figures it has to clear to stay alive and to branch, all before running anything.
- Given the shade table, you can say which row is still a whole plant under two crowns
and which is the last one alive under three, and defend both answers from the
alive atandbranches atcolumns. - explain why the scrub's digest is bit-for-bit identical on generous ground and on ground giving back a twelfth as much, and what that says about its strategy.
- Shown a row edited for a purpose and behaving identically, check which methods read the fields that moved before check anything else.
- list three things a real plant does that no filled-in row of this block can express, and say what it would cost to add one of them.
Exercise 1 — take away half the daylight. Before running
anything, work out what -light 6 does to each row's ceiling and which
rows lose a generation from their picture. Then check with
go run ./cmd/flora -mode row -light 6.
Income is light multiplied by the crown's share, so halving the light halves every income and every ceiling: 18, 36, 90 and 300. Now read those against the price lists. The moss keeps generation 2, which costs 1.60 against a ceiling of 18. The herb falls from 72 to 36, below the 50 grams generation 2 costs, so it drops to generation 1. The scrub falls from 180 to 90, below 160, so it drops from 3 to 2. The tree falls from 600 to 300, below 500, so it drops from 3 to 2 as well.
$ go run ./cmd/flora -mode row -light 6 | tail -5
soil income ceiling gen tall pass alive at branches at
moss 4 0.3600 18.00 2 0.04 0.6400 0.0833 0.1333
herb 6 1.8000 36.00 1 0.79 0.8000 0.1667 1.6667
scrub 12 0.7200 90.00 2 1.08 0.8929 0.0333 0.6667
tree 10 1.5000 300.00 2 9.00 0.5714 0.0400 0.4000
Three of the four rows look like a smaller kind of plant in half the light, and the one with the cheapest grammar comes through untouched. The two right-hand columns did not move at all, because neither of them mentions the light landing on the valley: they are both properties of the row, and the daylight is a property of the day.
Exercise 2 — find the rain that finally reaches the tree. Using
the soil and income columns, work out the moisture and
nutrient trickle at which the tree stops being limited by light, then run the four rows
beneath it.
The tree's roots find ten soil cells, so a trickle of r moisture a cell pays for 10 × r ÷ 0.60 units of energy, and that stops covering the tree's 3.0000 when r drops below 0.18. The nutrient side is 10 × f ÷ 0.15, which stops covering 3.0000 below 0.045. Run it a shade under both.
$ go run ./cmd/flora -mode alone -ticks 2000 -rain 0.17 -feed 0.044
flora: one plant of each kind, each alone in its own 12x8 valley at 4,1
12.00 of daylight, the ground putting back 0.17 of moisture and 0.044 of nutrient a cell a tick
mass height leaf gen segments limit world
moss 36.0000 0.0720 0.7200 2 16 light 05f1078fa6186607
herb 22.6667 0.4987 1.1333 1 5 water 4d6a94beed3b4704
scrub 180.0000 2.1600 4.5000 3 64 light 7c7adbc0c7bf6090
tree 566.6427 16.9993 16.9993 3 125 water 7e3e4a7216807baa
The tree's limit reads water and it is heading for
10 × 0.17 ÷ 0.60 = 2.8333 of income over 0.005 of upkeep, or 566.67
grams, which is where 566.6427 is two thousand ticks in. It holds generation 3,
because 566 is comfortably over the 500 that costs. The moss and the scrub come out
untouched here, digest for digest. Only one of those two came through the deeper
drought a few stages ago without moving a bit, and it is the one that spreads its
roots over twelve cells and spends the least water per unit of energy it fixes.
Exercise 3 — draw them as they stand under one crown. Take the
four ceilings from the one-crown row of the shade table and hand them back to the
drawing. Predict which of the five digests from the earlier render will be unchanged,
then run
go run ./cmd/flora -mode shot -grams 5.143,10.286,25.714,85.714.
A row's picture depends on its generation and on nothing else about the mass, so any
row whose generation is the same at both masses must produce the same digest. From
the shade table, only the moss keeps generation 2 under one crown. Its digest should
be the same 115643045c8a8fb1; the other three should all move, and so
should the combined frame.
$ go run ./cmd/flora -mode shot -grams 5.143,10.286,25.714,85.714
flora: 4 rows on the bare frame, out of row 110
grammar part seg grams gen segments w by h its own frame
moss fan 5 1.0 5.14 2 16 4 by 4 115643045c8a8fb1
herb sprig 8 2.0 10.29 1 5 3 by 6 77864a6318a2aa81
scrub fan 12 2.5 25.71 1 4 2 by 5 0ee59165ca46c447
tree sprig 14 3.0 85.71 1 5 3 by 9 1fc621fc9b7e19a4
the four together bd20113621b771e8febfd6a1ddb6bfb039f3c50076224c83bc40cde8ba7c592a
The moss is drawn to the pixel as it was at 36 grams, on a seventh of the tissue. The other three have collapsed into stubs of four or five strokes, and the tree that was twenty-seven pixels by eighty-one is three by nine: no wider than the herb beside it and three pixels taller. Understorey is a picture as much as it is a number, and the picture came out of the same three lines that drew the canopy.
Every plant above climbed to a size and stayed at it for however many ticks were left. Nothing here grows less at one time of year than at another, because there is no time of year: an open cell has received 12.00 units of daylight on every tick this volume has run and it will receive 12.00 on the ten millionth. That constant is doing more damage to these four rows than any of them can show. A plant that lives one summer and a plant that shuts down for a quarter of every year are not distinguishable in a valley where the sky never moves. Next: the year, as one angle gaining a fixed amount every tick, and read three ways.