The World Vol 4 · The Terrarium
ch 35 / 105
Chapter 35

Light In, Mass Out

One plant's tick budget

One plant on one cell has four accounts open every tick: light caught, water drawn, upkeep paid, and mass added or lost. A plant is a budget kept once a tick; income comes from light, the water bill prices that income, upkeep charges every gram already standing, and only the difference becomes new tissue.

The point of starting this small is that the ceiling is visible before the run starts. A square of ground does not get larger as the plant grows. Income stays flat. Upkeep grows with every gram the plant has already bought, so the two lines meet at one mass the code can predict.

That is the first living rule in The Hollow. Rock, water and soil have moved by seeded laws, walkers have crossed the map for free, and fire has consumed fuel and gone out. None of those things kept an account of what it needed to remain standing.

The new package is internal/terra. It keeps the terrarium's books: what a cell of ground offers in a tick, what the plant standing there can afford, and how much tissue remains after the bill. No random stream is read here, so the printed numbers are the same numbers wherever the code runs.

The 150.00-gram ceiling

Five constants and one definition. Twelve units of light land on the cell each tick. The plant's crown intercepts 0.25 of what lands. Fixing one unit of energy costs it 0.60 units of water, drawn out of the soil, and the cell can give up 2.50 units of water in a tick. Every gram of standing tissue charges 0.02 units a tick just to stay alive. The definition is the energy unit itself: one unit of leftover energy buys exactly one gram of tissue, because that is how the unit was picked. Real numbers for real plants come later; these were chosen so you can check the arithmetic.

Income first. 12.00 landing, 0.25 intercepted, so 12.00 × 0.25 = 3.00 units a tick. Paying for it costs 3.00 × 0.60 = 1.80 units of water, and the cell has 2.50 to give, so the water goes through without argument and the plant earns its full 3.00. Note what the income does not depend on: not the season, not the neighbours, and not the size of the plant. It is 3.00 on the first tick and 3.00 on the ten-thousandth.

Now the outgoings, starting from a seedling of 1.00 gram. Upkeep on tick 1 is 0.02 × 1.00 = 0.02 units. Income 3.00 minus upkeep 0.02 leaves 2.98, and 2.98 units buys 2.98 grams, so the plant ends the tick at 3.98 grams. Tick 2 charges upkeep on the new figure: 0.02 × 3.98 = 0.0796, leaving 2.9204 to build with, for a mass of 6.9004. Tick 3 charges 0.02 × 6.9004 = 0.138008 and builds 2.861992, reaching 9.762392. Tick 4 charges 0.19524784, builds 2.80475216, and stands at 12.56714416 grams.

The decimals stop being tidy on the third tick, which is what a charge proportional to a quantity that keeps changing does to a column of numbers. Something else is happening in that column too, and it is easy to miss at this size: the amount built fell from 2.98 to 2.9204 to 2.861992 to 2.80475216. Every tick the plant gets a little richer and every tick it builds a little less, because it is paying to keep the tissue it bought last time.

So ask the question the table is heading toward. When does the building stop? The plant grows by income minus upkeep, which is 3.00 minus 0.02 × mass, and that is zero when 0.02 × mass equals 3.00. Divide: mass = 3.00 ÷ 0.02 = 150.00 grams. Check it from both sides. At 149 grams the plant earns 3.00 and is charged 2.98, so it puts on two hundredths of a gram. At 151 grams it is charged 3.02 against the same 3.00 and loses two hundredths. 150 grams is where a plant on this cell settles whether it climbs to it or falls to it, and every number in that division came from the constants at the top of this section.

Income and upkeep plotted against a plant's standing mass A graph whose across axis is grams of tissue standing, from 0 to 200, and whose up axis is energy units a tick, from 0 to 4. A flat gold line sits at 3.00 units: the income, the same at every mass. A rising dashed red line runs from the origin, gaining 0.02 units for every gram: the upkeep. The vertical gap between them is what the plant has left to build with, marked at 50 grams where it measures 2.00. The two lines cross at 150 grams, where the gap closes and nothing is left to build with. INCOME AND UPKEEP AT EVERY MASS THE PLANT COULD HAVE 0 1 2 3 4 0 50 100 150 200 income = 12.00 × 0.25 = 3.00 the same at every mass, whatever the plant weighs 2.00 150.00 g upkeep eats it all upkeep = 0.02 × grams standing across: grams of tissue standing up: energy units a tick
Figure 35.1 — the whole chapter on one pair of axes. The bracket at 50 grams is the 2.00 units that plant has left after upkeep, and it is the height of that bracket, not the height of either line, that becomes tissue. The bracket shrinks as the plant walks rightward along its own graph, and closes at 150 grams.
∑ Math Interlude — the budget in five letters

Everything above was done in numbers. The shorthand earns its place only because the same five quantities are about to appear for four different species on thousands of cells. Give the light landing on a cell the name L, the crown's share c, the cell's water allowance w, the water spent per unit fixed t, the upkeep per gram u, and the plant's standing mass m. A dot between two letters means multiply, so L·c is 12.00 × 0.25.

The income is the smaller of two figures: what the crown can catch, and what the water will pay for. min(a, b) is the usual way to write "whichever of these two is smaller", and it is exactly one if in code.

income = min(L·c, w ÷ t)

Check it: L·c is 3.00, and w ÷ t is 2.50 ÷ 0.60 = 4.1667, so the income is 3.00 and the water is not what is holding this plant back. The change in mass over one tick gets the mark Δm, said "delta m", where the triangle means "how much this changed":

Δm = income − u·m

And the mass where that change is zero, written m*, is the one the last paragraph divided out: set Δm to 0, which puts u·m equal to the income, and divide both sides by u.

m* = income ÷ u

3.00 ÷ 0.02 = 150.00, which is the crossing in Figure 35.1 and the number the program is about to print before it takes a single tick.

Lenergy units of light landing on one cell in one tick; 12.00 here
cthe share of that light the crown intercepts, between 0 and 1; 0.25 here
wwater units the cell can give up in one tick; 2.50 here
twater units spent for each unit of energy fixed; 0.60 here
uenergy one gram of standing tissue costs per tick; 0.02 here
mgrams of tissue standing right now, the only number a tick changes
a·ba multiplied by b
min(a, b)whichever of the two numbers is smaller
Δmhow much m changes in one tick, negative when the plant shrinks
m*the mass where Δm is zero: income ÷ u, so 150.00 here

internal/terra ledger

Two types, because the numbers come from two different places. Light and water are facts about a square of ground and would be true if nothing were growing there. The crown's share, the thirst and the upkeep are facts about a kind of plant and travel with it wherever it is planted. Keeping them apart costs nothing today and is the difference between a small edit and a rewrite the day one cell has to answer to several plants.

One more thing to have in mind before the code. The world advances in fixed steps of a tenth of a second of wall time, and "once a tick" below means once per one of those steps, the same step the terrain and the walkers already move on. No hour, no second and no frame rate appears anywhere in terra. The books balance per tick, so playing the sim faster or slower changes how long you wait for a plant and changes nothing about the plant.

▣ Build · stage 1 — the ledger, and the ceiling it can predict
// internal/terra/terra.go
// Package terra keeps the terrarium's books: what one cell of ground
// offers in a tick, what the plant standing on it can afford out of
// that, and the tissue it has to show for the difference. Nothing in
// here draws anything, and nothing in here knows how long a tick is.
package terra

// Ground is what one cell hands a plant in a single tick. Both numbers
// are allowances rather than stores: whatever is not taken this tick is
// not saved for the next one.
type Ground struct {
	Light float64 // energy units of daylight landing on the cell
	Water float64 // water units the soil here can give up
}

// Plant is one plant's whole physiology. Mass is the only field a tick
// changes; the other three are what kind of plant this is.
type Plant struct {
	Mass   float64 // grams of tissue standing right now
	Catch  float64 // the share of its cell's light the crown intercepts
	Thirst float64 // water units spent per energy unit fixed
	Upkeep float64 // energy units one gram of tissue costs per tick
}

// Ledger is one tick's four numbers, in the order they are worked out.
type Ledger struct {
	Caught float64 // energy the crown fixed
	Drawn  float64 // water the roots pulled to pay for it
	Paid   float64 // energy the standing tissue charged
	Grown  float64 // grams the leftover bought; negative when short
}

// Fix is the energy this plant can turn over in one tick: its share of
// the light landing on the cell, or as much of that as the cell's water
// will pay for, whichever runs out first.
func (p Plant) Fix(g Ground) float64 {
	caught := g.Light * p.Catch
	if caught*p.Thirst > g.Water {
		return g.Water / p.Thirst
	}
	return caught
}

// Tick charges one plant one tick against one cell and hands back the
// four numbers that moved. The plant's mass is the only thing it
// changes, and it changes it exactly once.
func (p *Plant) Tick(g Ground) Ledger {
	l := Ledger{Caught: p.Fix(g)}
	l.Drawn = l.Caught * p.Thirst
	l.Paid = p.Mass * p.Upkeep
	l.Grown = l.Caught - l.Paid
	p.Mass += l.Grown
	return l
}

// Ceiling is the mass at which upkeep swallows the whole income, so
// nothing is left to build with. It is worked out from the four numbers
// and never measured from a run.
func (p Plant) Ceiling(g Ground) float64 { return p.Fix(g) / p.Upkeep }

Three decisions in that file matter. Fix hangs off a Plant value and Tick hangs off a pointer to one, and the split is the whole safety story of the package: asking a plant what it could earn can never alter it, and there is exactly one method in the world that may move Mass. Tick returns a Ledger instead of printing one, so the arithmetic and the reporting are separable, and a caller that wants a table and a caller that wants a census are the same tick underneath. And Ceiling takes no ticks at all. It is the paper division from the last section, in one line, available before the simulation starts and useful precisely because it never consults the run it is predicting.

◆ Note — what this budget leaves out, said plainly

This is physiology at the depth a valley needs, and it is a long way from the biochemistry. One number stands for all the tissue, so a plant that spent everything on roots and a plant that spent everything on leaves are the same plant here. A real leaf does not turn light into sugar by one multiplication: it saturates in bright light, slows down when it is cold, and burns some of its own sugar in the dark, none of which appears above. Nitrogen and phosphorus are not in the books at all. Nothing here can age, die, or set seed. The efficiency numbers were chosen to be legible, not measured; a real leaf converts a few percent of the light that strikes it, while 0.25 here is a crown's share of a cell's daylight, which is a friendlier quantity with a friendlier number. What the model does have to get right is the direction of the arrows and the fact that upkeep scales with size, because those two are what make a plant stop. The rest can be added a term at a time, and some of it is.

▣ Build · stage 2 — a hundred ticks, printed four columns wide
// cmd/terra/main.go — the bench: no window, no world, one plant

// head prints the cell, the plant and the two numbers that can be
// worked out before a single tick is taken.
func head(g terra.Ground, p terra.Plant) {
	fmt.Printf("one plant on one cell: %.2f light and %.2f water a tick\n", g.Light, g.Water)
	fmt.Printf("  crown catches %.2f of the light, thirst %.2f water a unit, upkeep %.2f a gram\n",
		p.Catch, p.Thirst, p.Upkeep)
	fmt.Printf("  income %.4f a tick, wanting %.4f of the %.2f water on offer\n",
		p.Fix(g), p.Fix(g)*p.Thirst, g.Water)
	fmt.Printf("  upkeep swallows that at %.4f / %.2f = %.4f grams\n",
		p.Fix(g), p.Upkeep, p.Ceiling(g))
}

// budget runs the books tick by tick and prints the four numbers plus
// the running mass, with the first four rows always shown because they
// are the four the reader worked out on paper.
func budget(g terra.Ground, p terra.Plant, ticks, every int, settle float64) {
	head(g, p)
	fmt.Printf("\n  %4s %10s %10s %10s %10s %11s\n",
		"tick", "caught", "drawn", "paid", "grown", "mass")
	stalled, at := 0, 0.0
	for t := 1; t <= ticks; t++ {
		l := p.Tick(g)
		if stalled == 0 && math.Abs(l.Grown) < settle {
			stalled, at = t, p.Mass
		}
		if t > 4 && t%every != 0 && t != ticks {
			continue
		}
		fmt.Printf("  %4d %10.4f %10.4f %10.4f %10.4f %11.4f\n",
			t, l.Caught, l.Drawn, l.Paid, l.Grown, p.Mass)
	}
	if stalled == 0 {
		fmt.Printf("  the tick's change is still over %.4f grams after %d ticks\n", settle, ticks)
		return
	}
	fmt.Printf("  the tick's change fell under %.4f grams at tick %d, %.4f grams standing\n",
		settle, stalled, at)
}
$ go run ./cmd/terra
one plant on one cell: 12.00 light and 2.50 water a tick
  crown catches 0.25 of the light, thirst 0.60 water a unit, upkeep 0.02 a gram
  income 3.0000 a tick, wanting 1.8000 of the 2.50 water on offer
  upkeep swallows that at 3.0000 / 0.02 = 150.0000 grams

  tick     caught      drawn       paid      grown        mass
     1     3.0000     1.8000     0.0200     2.9800      3.9800
     2     3.0000     1.8000     0.0796     2.9204      6.9004
     3     3.0000     1.8000     0.1380     2.8620      9.7624
     4     3.0000     1.8000     0.1952     2.8048     12.5671
    10     3.0000     1.8000     0.5154     2.4846     28.2562
    20     3.0000     1.8000     0.9699     2.0301     50.5264
    30     3.0000     1.8000     1.3413     1.6587     68.7228
    40     3.0000     1.8000     1.6447     1.3553     83.5906
    50     3.0000     1.8000     1.8926     1.1074     95.7387
    60     3.0000     1.8000     2.0952     0.9048    105.6646
    70     3.0000     1.8000     2.2607     0.7393    113.7747
    80     3.0000     1.8000     2.3959     0.6041    120.4013
    90     3.0000     1.8000     2.5064     0.4936    125.8157
   100     3.0000     1.8000     2.5967     0.4033    130.2397
  the tick's change is still over 0.0100 grams after 100 ticks

The first four rows are the four you did by hand, printed to four places: 3.9800, 6.9004, 9.7624 and 12.5671. Nothing was fitted and nothing was tuned. The two left-hand columns never move, because the light landing on the cell does not care how big the plant gets and the water bill is a fixed multiple of an income that is itself fixed. All the motion is in paid, climbing toward 3.0000, and in grown, falling away from it. The mass column is the two of them added up over and over, and at tick 100 it stands at 130.2397 with 19.76 grams of headroom left under the 150.0000 the header predicted on the first line of output.

Run it for three hundred ticks and the interesting part is how it arrives.

▣ Build · stage 3 — the flags, and the cell that runs dry
// cmd/terra/main.go — every constant in the chapter is a flag, so a
// prediction can be checked by changing one of them
	light := flag.Float64("light", 12, "energy units landing on the cell each tick")
	water := flag.Float64("water", 2.5, "water units the cell can give up each tick")
	catch := flag.Float64("catch", 0.25, "share of the cell's light the crown intercepts")
	thirst := flag.Float64("thirst", 0.6, "water units spent per energy unit fixed")
	upkeep := flag.Float64("upkeep", 0.02, "energy one gram of tissue costs a tick")
	mass := flag.Float64("mass", 1, "grams of tissue the plant starts with")
	ticks := flag.Int("ticks", 100, "how many ticks to run")
	every := flag.Int("every", 10, "print a row every this many ticks")
	settle := flag.Float64("settle", 0.01, "the growth per tick counted as stalled")
$ go run ./cmd/terra -ticks 300 -every 50
one plant on one cell: 12.00 light and 2.50 water a tick
  crown catches 0.25 of the light, thirst 0.60 water a unit, upkeep 0.02 a gram
  income 3.0000 a tick, wanting 1.8000 of the 2.50 water on offer
  upkeep swallows that at 3.0000 / 0.02 = 150.0000 grams

  tick     caught      drawn       paid      grown        mass
     1     3.0000     1.8000     0.0200     2.9800      3.9800
     2     3.0000     1.8000     0.0796     2.9204      6.9004
     3     3.0000     1.8000     0.1380     2.8620      9.7624
     4     3.0000     1.8000     0.1952     2.8048     12.5671
    50     3.0000     1.8000     1.8926     1.1074     95.7387
   100     3.0000     1.8000     2.5967     0.4033    130.2397
   150     3.0000     1.8000     2.8531     0.1469    142.8039
   200     3.0000     1.8000     2.9465     0.0535    147.3794
   250     3.0000     1.8000     2.9805     0.0195    149.0457
   300     3.0000     1.8000     2.9929     0.0071    149.6525
  the tick's change fell under 0.0100 grams at tick 283, 149.5100 grams standing
$ go run ./cmd/terra -water 1.2 -ticks 300 -every 50
one plant on one cell: 12.00 light and 1.20 water a tick
  crown catches 0.25 of the light, thirst 0.60 water a unit, upkeep 0.02 a gram
  income 2.0000 a tick, wanting 1.2000 of the 1.20 water on offer
  upkeep swallows that at 2.0000 / 0.02 = 100.0000 grams

  tick     caught      drawn       paid      grown        mass
     1     2.0000     1.2000     0.0200     1.9800      2.9800
     2     2.0000     1.2000     0.0596     1.9404      4.9204
     3     2.0000     1.2000     0.0984     1.9016      6.8220
     4     2.0000     1.2000     0.1364     1.8636      8.6856
    50     2.0000     1.2000     1.2642     0.7358     63.9472
   100     2.0000     1.2000     1.7321     0.2679     86.8707
   150     2.0000     1.2000     1.9024     0.0976     95.2187
   200     2.0000     1.2000     1.9645     0.0355     98.2588
   250     2.0000     1.2000     1.9871     0.0129     99.3659
   300     2.0000     1.2000     1.9953     0.0047     99.7691
  the tick's change fell under 0.0100 grams at tick 263, 99.5124 grams standing

Be careful about the word "stall", because the long run shows the honest version. The plant never reaches 150.0000 grams. Each tick it closes 0.02 of whatever gap is left, so the gap is multiplied by 0.98 every tick and gets smaller forever without getting to zero: 7.20 grams short at tick 150, 0.95 short at tick 250, 0.35 short at tick 300. A program cannot report a stall it never has, so it reports the thing it can defend, which is the first tick where a whole tick of growth came to less than a hundredth of a gram. That is tick 283. The threshold is a flag because it is a judgement, and the ceiling above it is not a judgement at all.

The second run is the water column earning its place. Turn the cell's allowance down to 1.20 and the crown's 3.00 is unaffordable: 1.20 ÷ 0.60 pays for exactly 2.00 units, the caught column drops to 2.00 without a single leaf changing, and the header prints a ceiling of 100.0000 grams before the loop starts. Two-thirds of the water buys two-thirds of the plant. The plant itself is the same plant, standing on worse ground.

The runaway crown

One line in terra.go invites a fair objection: a bigger plant has more leaf, so surely it catches more light. Catch being a constant seems to say a seedling and a full-grown plant intercept the same share of the same square, which cannot be true. So change it. Let the share grow with the mass, at 0.05 for every gram standing, and the seedling starts at 0.05 and earns its way up.

⚠ Worked failure — a share that was allowed past all of it
// cmd/terra/main.go — the crown's share recomputed from the mass at
// the top of every tick, and an extra column reporting how much of the
// cell's whole daylight the plant walked off with
func runaway(g terra.Ground, p terra.Plant, share float64, ticks, every int) {
	fmt.Printf("crown share %.2f a gram, on %.2f light and %.2f water a tick\n",
		share, g.Light, g.Water)
	fmt.Printf("  %5s %9s %9s %9s %9s %9s %11s\n",
		"tick", "share", "caught", "of light", "paid", "grown", "mass")
	over := 0
	for t := 1; t <= ticks; t++ {
		p.Catch = share * p.Mass
		if over == 0 && p.Catch > 1 {
			over = t
		}
		l := p.Tick(g)
		if t > 4 && t%every != 0 && t != ticks {
			continue
		}
		fmt.Printf("  %5d %9.4f %9.4f %9.4f %9.4f %9.4f %11.4f\n",
			t, p.Catch, l.Caught, l.Caught/g.Light, l.Paid, l.Grown, p.Mass)
	}
	fmt.Printf("  the crown's share passed 1.0000 at tick %d\n", over)
	fmt.Printf("  it settles at %.4f grams, harvesting %.4f of the %.2f that lands\n",
		p.Ceiling(g), p.Fix(g), g.Light)
}
$ go run ./cmd/terra -mode runaway -ticks 120 -every 20
crown share 0.05 a gram, on 12.00 light and 2.50 water a tick
   tick     share    caught  of light      paid     grown        mass
      1    0.0500    0.6000    0.0500    0.0200    0.5800      1.5800
      2    0.0790    0.9480    0.0790    0.0316    0.9164      2.4964
      3    0.1248    1.4978    0.1248    0.0499    1.4479      3.9443
      4    0.1972    2.3666    0.1972    0.0789    2.2877      6.2320
     20    2.9373    4.1667    0.3472    1.1749    2.9918     61.7372
     40    5.4234    4.1667    0.3472    2.1693    1.9973    110.4646
     60    7.0831    4.1667    0.3472    2.8332    1.3334    142.9954
     80    8.1911    4.1667    0.3472    3.2765    0.8902    164.7132
    100    8.9309    4.1667    0.3472    3.5724    0.5943    179.2122
    120    9.4248    4.1667    0.3472    3.7699    0.3968    188.8918
  the crown's share passed 1.0000 at tick 9
  it settles at 208.3333 grams, harvesting 4.1667 of the 12.00 that lands

Read that run the way somebody who wanted the change would read it, and it is a success. The seedling grows slowly at first and then faster, which is what real seedlings do. The harvest settles at 4.1667 units, comfortably under the 12.00 that lands on the cell. The plant stops growing, at 208.3333 grams instead of 150, and if 208 grams is the number you wanted then here is a model that produces it. Nothing on the page says "wrong".

The share column does, once you know what it is a share of. It passed 1.0000 at tick 9 and reads 9.4248 by tick 120, and a share is a fraction of something. This crown claims 942% of its cell's daylight. It is not doing so in the caught column only because the water allowance stepped in first: 2.50 ÷ 0.60 caps the harvest at 4.1667, and it was pinned there long before tick 20, which is why "of light" sits at a respectable 0.3472 while the share behind it climbs with nothing to stop it. The water bill is hiding a broken model, and a hidden bug is worse than one that crashes. To see what it was hiding, stand the same plant on wet ground.

$ go run ./cmd/terra -mode runaway -water 1000 -ticks 120 -every 20
crown share 0.05 a gram, on 12.00 light and 1000.00 water a tick
   tick     share    caught  of light      paid     grown        mass
      1    0.0500    0.6000    0.0500    0.0200    0.5800      1.5800
      2    0.0790    0.9480    0.0790    0.0316    0.9164      2.4964
      3    0.1248    1.4978    0.1248    0.0499    1.4479      3.9443
      4    0.1972    2.3666    0.1972    0.0789    2.2877      6.2320
     20  267.8449 1666.6667  138.8889  107.1380 1559.5287   6916.4271
     40 1563.7822 1666.6667  138.8889  625.5129 1041.1538  32316.7976
     60 2428.9602 1666.6667  138.8889  971.5841  695.0826  49274.2874
     80 3006.5600 1666.6667  138.8889 1202.6240  464.0427  60595.2427
    100 3392.1702 1666.6667  138.8889 1356.8681  309.7986  68153.2028
    120 3649.6067 1666.6667  138.8889 1459.8427  206.8240  73198.9572
  the crown's share passed 1.0000 at tick 8
  it settles at 83333.3333 grams, harvesting 1666.6667 of the 12.00 that lands

83 kilograms of plant on one square of ground, harvesting 1666.6667 units of light off a cell that receives 12.00. The "of light" column says 138.8889, which is the plant eating a hundred and thirty-nine times the daylight that arrives. The two runs together give the diagnosis: a plant's final size should not be decided by how much water the ground has going spare, and here it is, because the only thing capping the harvest was the water. Take the cap off and there is nothing underneath it.

The rule the mistake teaches is small and general. A share is bounded by the thing it is a share of. Any quantity a model takes out of a place has to be checked against what that place holds, and the check belongs where the taking happens, not wherever the numbers happen to run out. A crown's share is a fraction between 0 and 1 and a line that computes it needs a ceiling of 1 written into it. This page goes the other way and keeps Catch a constant, because one plant's crown growing over its own cell is a different question from the one the budget is answering, and mixing them is how you end up with two ceilings and no idea which one you are looking at.

Mass buys the drawing

A number in a table is not a plant. The valley already knows how to grow a picture of one: a grammar that rewrites a short string into a long one, and a turtle that reads the long one back as a list of straight segments. That machinery takes a generation count and draws whatever that count produces, and until now the count was typed by a person. The budget can supply it instead, and the link between them is a price.

The sprig grammar puts five F's where each F was, so a plant at generation 0 draws 1 segment, at generation 1 draws 5, at 2 draws 25, at 3 draws 125, at 4 draws 625. Decide that one drawn segment of stalk stands for one gram of tissue and the generations acquire prices: 1, 5, 25, 125 and 625 grams. A plant draws the largest generation it can pay for out of the mass it is standing up.

▣ Build · stage 4 — the drawing bought out of the ledger
// internal/terra/form.go

// Form is what a grammar costs to build. One drawn segment stands for
// SegMass grams of stalk, and every generation multiplies the number of
// segments by Fan, so generation n needs Fan^n segments standing. Max is
// the generation the grammar is never grown past, so a runaway number
// cannot ask the turtle for a million strokes.
type Form struct {
	SegMass float64
	Fan     float64
	Max     int
}

// Cost is the tissue generation n needs standing before it can be drawn.
func (f Form) Cost(n int) float64 {
	c := f.SegMass
	for i := 0; i < n; i++ {
		c *= f.Fan
	}
	return c
}

// Stage is the largest generation this much tissue can pay for.
func (f Form) Stage(mass float64) int {
	n := 0
	for n < f.Max && f.Cost(n+1) <= mass {
		n++
	}
	return n
}
// cmd/terra/main.go — draw the plant on every tick its tissue can pay
// for one more generation of stalk, and hash the frame each time

// picture draws one plant into a fresh buffer and hands back the
// segments and the frame hash. twig picks one of three greens from how
// many brackets deep the turtle was, the way the plant chapter painted
// its stalks.
func picture(gen int, seg float64, w, h int) ([]grow.Segment, grow.Box, string) {
	n := grow.Named["sprig"]
	pl := grow.Plant{Axiom: n.Axiom, Rules: n.Rules, Gen: gen, Seg: seg,
		Turn: field.Turn / 8, Up: field.Turn / 4}
	_, segs := pl.Sprout(vec.Vec2{X: float64(w) / 2, Y: float64(h) - 2})
	b := render.NewBuffer(w, h)
	b.Fill(render.Void)
	for _, s := range segs {
		b.Line(int(math.Round(s.A.X)), int(math.Round(s.A.Y)),
			int(math.Round(s.B.X)), int(math.Round(s.B.Y)), twig(s.Depth))
	}
	return segs, grow.Bounds(segs), b.Hash()
}

The run below names no geometry, because all of it sits in flag defaults, and the four digests it prints cannot be checked without it. The frame is 128 pixels across and 128 down. One segment carries the turtle 3 pixels. The turtle starts at (w/2, h-2), the middle of the bottom edge and two rows up from it, so the plant grows up into the frame from the ground it is standing on. Change any one of those three and all four digests change with them.

$ go run ./cmd/terra -mode form -ticks 300
one plant on one cell: 12.00 light and 2.50 water a tick
  crown catches 0.25 of the light, thirst 0.60 water a unit, upkeep 0.02 a gram
  income 3.0000 a tick, wanting 1.8000 of the 2.50 water on offer
  upkeep swallows that at 3.0000 / 0.02 = 150.0000 grams
  one segment of stalk stands for 1.00 grams, 5 segments a generation

  tick        mass    gen   gen costs   segments    w by h  frame
     0      1.0000      0        1.00          1    0 by  3  a82dc9e70a3846be
     2      6.9004      1        5.00          5    4 by  9  e71f02e589d35eed
     9     25.7716      2       25.00         25   14 by 27  97c796700671c9f0
    89    125.3222      3      125.00        125   41 by 81  a4ae5f902f5d4f06
  generation 4 would need 625.00 grams, and the ceiling is 150.0000

Four drawings in three hundred ticks, and the gaps between them stretch out because each generation costs five times the one before while the plant's income is flat. Generation 1 arrives on tick 2, generation 2 on tick 9, generation 3 on tick 89. Generation 4 never arrives at all, and the last line says why in numbers already on the page: it wants 625 grams standing and the ceiling is 150. This plant is a three-generation plant forever, and nobody wrote a 3 anywhere. The 3 is what 12.00 units of light, a quarter of them caught, and two hundredths of upkeep a gram add up to on a grammar that quintuples.

The frame hash is over the finished pixels: buffer size first, then every pixel in reading order. Since the frame, the segment length, the turn and the grammar are all fixed here, the picture is a function of the generation and nothing else, so those four digests pin four drawings that must come out identical on any machine. The last one measures 41 by 81 pixels: twenty-seven times the height of the single three-pixel stroke the seedling was, bought with a hundred and twenty-five times the tissue, both of those facts coming out of the same column of arithmetic.

The predicted ceiling

Strip the plant out and what is left is a pattern that will run through the rest of this book. A quantity accumulates. Something adds to it at a rate that does not depend on how much is already there. Something else takes away at a rate proportional to how much is already there. Those two facts alone guarantee a settling point, at inflow divided by the proportional rate, approached by closing the same fraction of the remaining distance every step. A cup of tea cooling in a room does it. A bucket with a hole in it under a running tap does it. A population whose deaths scale with its size and whose births do not does it. The plant is the first one this book builds, and it will not be the last.

The reason to care about the pattern rather than the plant is that it tells you where to look when a number comes out wrong. If a plant settles somewhere unexpected, exactly two things can be responsible: the income, or the per-gram charge. Both are visible in the header line before the run starts. The worked failure above is the same reasoning run backwards, where a ceiling of 208.3333 was traceable to an income that had quietly become a function of water instead of light.

There is a standard hiding in that, and the rest of this volume holds to it. If a number decides how big something gets or how many of them there are, it should be computable from the constants and checkable against the run, not discovered by watching and then written down. 150.0000 was on the first line of output before a tick was taken, and the run spent 283 ticks agreeing with it. A model whose important numbers can only be found by running it is a model you cannot reason about, and an ecology is hard enough to reason about already.

✓ Checkpoint — one plant's books, closed
  • Given light on a cell, a crown's share, an upkeep per gram and a starting mass, I can fill in four ticks of caught, paid, grown and mass with a calculator, and check them against the printed table digit for digit.
  • compute a plant's ceiling as income divided by upkeep, and show that it is the same number whether the plant climbs to it from 1 gram or falls to it from 200.
  • explain why the plant never actually reaches 150.0000, and defend reporting a stall as the first tick whose growth fell under a stated threshold.
  • Handed a cell with a water allowance of 1.20 and a thirst of 0.60, you can work out the income of 2.00 and the ceiling of 100.0000 before running anything.
  • Shown a plant harvesting 1666.6667 units on a cell that receives 12.00, check the crown's share first, and you can name the bound it broke.
  • price each generation of a grammar that quintuples its segments, and say which generations a given ceiling puts permanently out of reach.
⚡ Exercises — try first, then reveal
Exercise 1 — start it too big. Run go run ./cmd/terra -mass 200 -ticks 100 -every 25. Predict the sign of the grown column and the value the mass column is heading for, then check.

Income is still 3.0000 and upkeep on 200 grams is 4.0000, so the first tick's grown is −1.0000 and the plant ends the tick at 199.0000. Every row after it is negative and shrinking: −0.9800, −0.9604, −0.9412, and −0.1353 by tick 100 with 156.6310 grams standing. The target is the same 150.0000 the header prints, approached from above at the same 0.98 a tick. A plant too big for its cell does not sit there being too big; it burns tissue it cannot pay for until it fits. The line in Tick that allows it is p.Mass += l.Grown with no floor under it, and nothing special had to be written for the shrinking case.

Exercise 2 — make it twice as expensive to be alive. Double the upkeep with go run ./cmd/terra -upkeep 0.04 -ticks 300 -every 100. Predict the ceiling and say whether the plant reaches its stall sooner or later than the 283 ticks the default took.

The ceiling halves to 3.0000 ÷ 0.04 = 75.0000, and the run prints exactly that on its fourth line. The stall arrives at tick 141, roughly half of 283, which catches people out: the expensive plant is only half the size but it gets there in half the time. The reason is that the same constant sets both. Each tick now closes 0.04 of the gap instead of 0.02, so the distance to the ceiling falls twice as fast while the ceiling itself is twice as close. Cheap tissue makes a big plant slowly; expensive tissue makes a small one quickly.

Exercise 3 — find the water that costs a branching. Work out the water allowance whose ceiling lands exactly on the 125.00 grams generation 3 costs, run -mode form at that allowance for 3,000 ticks, then run it again a tenth of a unit wetter.

Set the ceiling to 125: income must be 125 × 0.02 = 2.50, and an income of 2.50 needs 2.50 × 0.60 = 1.50 units of water. Run go run ./cmd/terra -mode form -water 1.5 -ticks 3000 and the plant stops at generation 2 with 25 segments, having spent three thousand ticks creeping toward a ceiling that is exactly the price of generation 3 and can therefore never quite pay it. Run it at -water 1.6 and the ceiling moves to 133.3333, generation 3 arrives on tick 137, and the frame hash is a4ae5f902f5d4f06: the same digest as the default run's generation 3, because the drawing depends on the generation and not on the route the plant took to afford it. A tenth of a unit of water a tick is the difference between a plant that branches three times and one that never does, and that is the whole argument for what comes next.