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

Tall Plants Eat First

The light field

Two plants can stand side by side, share no roots, and still fight over the same daylight. A one-gram seedling under a 150-gram neighbour should not earn the same 3.00 units as a seedling in the open. Light is a field the plants write before any plant eats: each crown casts shade tallest first, and a plant reads the light left at its cell immediately before its own crown is written into the grid.

Root competition is even-handed because two roots can both draw from the same store until it reaches the refill rate. Light arrives from above once. The crown that intercepts it takes its share before the shorter plant below has a budget at all.

The chapter holds the bed steady so the measurement names shade and nothing else. Water and nutrient stay full; the only moving number is the daylight a plant is allowed to fix.

The half-light handoff

A crown needs three numbers, and all three turn grams into something about the plant's top half. How far it reaches: 1.5 cells, so its own cell and the eight around it, which is the same nine-cell disc a root of that radius covers. How much leaf a gram of tissue carries: 0.03 of a cell's worth. How tall a gram stands: 0.04 of a cell.

Take the plant at its ceiling, 150 grams. It carries 0.03 × 150 = 4.50 cells of leaf, spread across the 9 cells it reaches, so every cell under it lies beneath 0.50 of a cell's worth of leaf. Half of each cell is covered and half is not, so half the light arriving from above carries on through. Height comes out of the same mass: 0.04 × 150 = 6.00 cells tall, which is the number that decides who reads first and takes no part in the arithmetic after that.

Now the seedling underneath. It has 12.00 × 0.50 = 6.00 units of daylight overhead instead of 12.00, so its crown catches 6.00 × 0.25 = 1.50 units a tick, and upkeep still charges 0.02 for every gram it is carrying. Its ceiling is income divided by upkeep, the same division as ever: 1.50 ÷ 0.02 = 75.00 grams. Put a second grown crown over it and the light halves again to 3.00, income falls to 0.75, and the ceiling lands on 37.50. A third takes it to 18.75. Every crown overhead costs the plant below half of everything it will ever be.

Walk the shaded seedling out by hand for three ticks and it grows the way the open one did, only smaller. Tick 1: upkeep 0.02 × 1.00 = 0.02, income 1.50, so 1.48 grams go on and it stands at 2.48. Tick 2 charges 0.049600 and builds 1.450400, reaching 3.930400. Tick 3 charges 0.078608, builds 1.421392, and leaves it at 5.351792 grams.

The pace is the surprise. This plant is heading for 75 instead of 150, and it is not taking any longer about it. Its distance from the ceiling at the start of tick 1 is 74.00 grams, and 74.00 × 0.98 = 72.52, which is exactly 75.00 − 2.48. Every tick closes 0.02 of whatever gap is left, whichever ceiling the plant is heading for, because the gap shrinks by upkeep multiplied by the gap and the income cancels out of that entirely. Shade decides where a plant stops. It has nothing to say about how long the stopping takes.

∑ Math Interlude — cover, and what gets past it

Three quantities and one multiplication. Write the leaf a gram carries as f, the number of cells the crown reaches as k, and the plant's mass as m, all of which are already numbers: 0.03, 9 and 150. The leaf the crown is carrying is f·m, and spread over k cells the amount lying over any one of them is:

cover = f·m ÷ k

0.03 × 150 ÷ 9 = 0.50. A cover of 0.50 means half a cell's worth of leaf over one cell, so half the light hits leaf and half of it misses. What gets past is everything not covered, and a cell cannot be more than entirely covered, so the figure is capped at 1:

pass = 1 − min(cover, 1)

Two crowns over one cell multiply: the second one gets the half the first let through and passes half of that, leaving 0.50 × 0.50 = 0.25 of the sky. Three leave 0.125. The general form for n identical crowns is pass multiplied by itself n times, written with a raised number:

light = L × passⁿ

And the ceiling follows from the ledger without a new idea in it. Income is the light reaching the plant multiplied by its crown's share c, and the ceiling is income divided by upkeep u:

m* = L × passⁿ × c ÷ u

12.00 × 0.5 × 0.25 ÷ 0.02 = 75.00 under one crown, and every further crown halves it. Multiplication is what makes shade stack the way it does: adding the covers up would let four crowns of 0.30 block 120% of the daylight, and multiplying their passes can never take a cell below zero however many of them there are.

fcells of leaf area one gram of tissue carries; 0.03 here
khow many cells the crown reaches, counted from its radius; 9 at a radius of 1.5
mgrams of tissue standing, the same m the ledger has been moving
coverhow much of one cell the crown's leaf lies over, 0 to 1
passthe share of the light arriving above a crown that gets through it
passⁿpass multiplied by itself n times: what gets through n crowns stacked
Ldaylight landing on an open cell; 12.00
m*the mass where upkeep eats the whole income, so growth stops
▣ Build · stage 1 — the table this page is arguing about
$ go run ./cmd/canopy -mode shade
canopy: a seedling under grown neighbours, each standing at 150.00 grams
        each crown spreads 4.50 cells of leaf over 9 cells, so 0.5000 of the light gets through

   crowns       light      income     ceiling   grams at 100
        0     12.0000      3.0000    150.0000       130.2397
        1      6.0000      1.5000     75.0000        65.1862
        2      3.0000      0.7500     37.5000        32.6594
        3      1.5000      0.3750     18.7500        16.3960
        4      0.7500      0.1875      9.3750         8.2643

Read the last column against the one before it. The ceilings halve exactly, by construction. The masses at tick 100 nearly halve and miss by a little: 65.1862 where half of 130.2397 would be 65.1198. The difference is the gram the seed started with. After a hundred ticks 0.1326 of that original gram is still in the plant, unscaled by anything, because no amount of shade can take away tissue the plant did not have to earn. Everything else in the column is bought with light and everything bought with light halves.

◆ Note — what this crown does that a real one does not

The model above is a disc of evenly spread leaf that dims the cells beneath it, and real canopies are less obliging. Sunlight arrives at an angle, so a real shadow falls to one side and walks across the ground through the day and through the year; this one falls straight down and never moves. Leaves inside a crown shade each other, so doubling a tree's leaf does not double what it intercepts, and the honest curve for light through a canopy falls away steeply at first and then flattens, where the straight subtraction here keeps its slope until the cell is covered. Nothing here has a trunk, which is a large object that casts shade and carries no leaf at all. And leaf area does not really track mass by one multiplication: a sapling is nearly all leaf and an old tree is nearly all wood.

What the model has to get right is smaller than any of that and it gets it right: shade depends on how much leaf is up there, it stacks when crowns overlap, it can never remove more light than arrived, and who it falls on is decided by height. Those four are what make a canopy a canopy. The rest are corrections to a curve, and a curve has to exist before anybody can correct it.

The shade pass

The grid comes first, and it is deliberately thin: one number per cell, wiped and rebuilt every tick. Shade is not a store and must never behave like one. The soil holds water from one tick to the next because water that was not drunk is still there; shade holds nothing, because it is a fact about what is standing right now and evaporates the instant the plant casting it does.

▣ Build · stage 2 — one number a cell, wiped every tick
// internal/terra/sky.go

// Sky is the light field: one number for every cell of the valley,
// wiped and rebuilt from scratch every tick. Full is what lands on a
// cell with nothing standing over it, and every number in here is that
// figure with some crowns multiplied into it.
type Sky struct {
	W, H int
	Full float64
	lit  []float64
}

// NewSky lays a light field over a grid of this size. It opens in full
// daylight, which is what it goes back to at the top of every tick.
func NewSky(w, h int, full float64) *Sky {
	s := &Sky{W: w, H: h, Full: full, lit: make([]float64, w*h)}
	s.Clear()
	return s
}

// index is one cell's place in the flat slice; In is whether a
// coordinate names a cell of this sky at all. Every reader and writer
// below goes through the pair, so a crown hanging over the rim asks
// about cells that are not there and gets told so.
func (s *Sky) index(c sim.Coord) int { return c.Y*s.W + c.X }

func (s *Sky) In(c sim.Coord) bool {
	return c.X >= 0 && c.X < s.W && c.Y >= 0 && c.Y < s.H
}

// Clear puts every cell back into full daylight. It runs once at the
// top of every tick, because shade is not a store: it is recomputed
// from what is standing right now.
func (s *Sky) Clear() {
	for i := range s.lit {
		s.lit[i] = s.Full
	}
}

// At is the light reaching one cell, or nothing at all off the grid.
func (s *Sky) At(c sim.Coord) float64 {
	if !s.In(c) {
		return 0
	}
	return s.lit[s.index(c)]
}

// Dim multiplies one cell's light by the share a crown lets through.
// Multiplying is what makes two crowns over one cell stack: half of a
// half is a quarter, and no order of shading can take a cell below
// zero or above the sky.
func (s *Sky) Dim(c sim.Coord, pass float64) {
	if !s.In(c) {
		return
	}
	s.lit[s.index(c)] *= pass
}

// Under is the geometry of a crown: every cell of the grid whose centre
// lies within r cells of c, walked in row order. It is the same disc
// arithmetic a root reaches its cells with, applied upward.
func (s *Sky) Under(c sim.Coord, r float64) []sim.Coord {
	n := int(r)
	var out []sim.Coord
	for dy := -n; dy <= n; dy++ {
		for dx := -n; dx <= n; dx++ {
			if float64(dx*dx+dy*dy) > r*r {
				continue
			}
			p := c.Offset(dx, dy)
			if s.In(p) {
				out = append(out, p)
			}
		}
	}
	return out
}

Dim multiplies and never subtracts, and that one operator is doing more work than it looks. Subtracting covers would let enough crowns take a cell past zero into negative light, which is the same class of mistake as a store handing out water it did not have. Multiplying passes cannot: every factor sits between 0 and 1, so the product does too, and a cell under nine crowns holds a very small positive number instead of an impossible one. The cap belongs on pass, at the one place a cover is turned into a fraction, and the field itself needs no floor at all.

▣ Build · stage 3 — the crown, and a plant that is standing somewhere
// internal/terra/sky.go

// Canopy is the part of a plant that stands between the sky and the
// ground. All three numbers turn grams of tissue into something about
// the crown, and all three belong to a kind of plant rather than to an
// individual.
type Canopy struct {
	Crown float64 // how far from its own cell the crown reaches, in cells
	Leafy float64 // cells of leaf area carried per gram of standing tissue
	Tall  float64 // cells of height per gram of standing tissue
}

// Height is how far off the ground the top of this crown is. Height
// decides nothing about how much light a plant catches and everything
// about who catches it first.
func (c Canopy) Height(mass float64) float64 { return c.Tall * mass }

// Leaf is the leaf area this much tissue is carrying, measured in cells
// of ground it could cover if it were laid out flat.
func (c Canopy) Leaf(mass float64) float64 { return c.Leafy * mass }

// Spread is how many cells a crown of this radius covers when the whole
// disc is on the grid. The leaf is divided by this figure wherever the
// plant stands, so a crown hanging over the rim loses the leaf that
// hung over it instead of packing it into the cells that are left.
func (c Canopy) Spread() int {
	n, out := int(c.Crown), 0
	for dy := -n; dy <= n; dy++ {
		for dx := -n; dx <= n; dx++ {
			if float64(dx*dx+dy*dy) <= c.Crown*c.Crown {
				out++
			}
		}
	}
	return out
}

// Pass is the share of the light arriving above this crown that gets
// through it to one cell underneath. The leaf is spread evenly over the
// disc, a cell covered by more leaf than it has room for is covered
// once, and what is not covered goes straight through.
func (c Canopy) Pass(mass float64) float64 {
	cover := c.Leaf(mass) / float64(c.Spread())
	if cover > 1 {
		cover = 1
	}
	return 1 - cover
}

// Stand is one plant standing somewhere: the budget of the last two
// chapters, the crown it carries, the ground it is rooted in, and the
// light that reached the top of that crown this tick.
type Stand struct {
	At    sim.Coord
	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
}

// Height is the top of this stand's crown right now: the crown's height
// per gram against the grams this particular plant has managed to stand
// up. It is the only number the caster sorts on.
func (st *Stand) Height() float64 { return st.Crown.Height(st.Plant.Mass) }

Spread counts the disc from the radius alone and not from where the plant happens to be. A crown at the rim of the map has part of its disc off the grid, and dividing its leaf by the cells that remain would make it darker for standing near an edge, which is an artefact of the map and not a fact about plants. The leaf that hangs over the edge is lost. Height hangs off the stand rather than the canopy because it is a question about an individual and not about a kind: the crown knows how tall a gram stands and only the stand knows how many grams there are. Lit is the one field on a Stand that neither the plant nor the crown owns: it is this tick's reading, written by the caster, read by the harvest, and meaningless in between.

Which leaves the tick itself, and it has two halves for a reason this book has already paid for twice. The walkers work every force out from the state the tick opened in and step afterwards, so a body updated early cannot change the answer a body updated late gets. The automaton copies its whole grid before a sweep, so no cell reads a neighbour that has already moved on. Deciding and applying are kept apart both times, and the argument here is the same one a third time: if a plant harvested the instant it was cast, it would grow, and its new mass would deepen the shade over a neighbour not yet reached, which would then be paying this tick for tissue that did not exist when the tick began.

▣ Build · stage 4 — cast every crown, then let everything eat
// internal/terra/sky.go

// Cast is the first half of a tick. The sky is wiped back to full
// daylight and every crown is written into it, tallest first, and each
// stand takes its reading at the moment before its own leaves go in.
// The number a stand keeps is therefore the light that got past
// everything taller than it, and past nothing else. No mass moves here.
func Cast(s *Sky, stands []*Stand) {
	s.Clear()
	order := make([]int, len(stands))
	for i := range order {
		order[i] = i
	}
	slices.SortStableFunc(order, func(a, b int) int {
		ha, hb := stands[a].Height(), stands[b].Height()
		switch {
		case ha > hb:
			return -1
		case ha < hb:
			return 1
		}
		return a - b // exactly equal heights: the older stand reads first
	})
	for _, i := range order {
		st := stands[i]
		st.Lit = s.At(st.At)
		pass := st.Crown.Pass(st.Plant.Mass)
		for _, c := range s.Under(st.At, st.Crown.Crown) {
			s.Dim(c, pass)
		}
	}
}

// Harvest is the second half. Every stand spends the reading Cast left
// it, in the order the world stores them, and this is the only half
// that moves a gram. Nothing in here touches the sky, so a plant that
// grew this tick casts the shadow of the plant it grew into on the next
// tick and not on this one.
func Harvest(stands []*Stand) []Ledger {
	out := make([]Ledger, len(stands))
	for i, st := range stands {
		out[i] = st.Plant.Tick(Ground{Light: st.Lit, Water: st.Water})
	}
	return out
}

Two lines in Cast carry the whole chapter. Sorting by height descending is what makes the competition one-sided: by the time a stand takes its reading, every plant above it has already dimmed the cell and every plant below it has not been reached, so the number is the light that got past the taller ones and nothing else. And taking the reading before writing its own shade is how a plant avoids standing in its own shadow, which would otherwise charge every plant in the world for the leaves it is holding up. Ties fall to the older stand by index, so two plants of exactly identical height are settled by one arbitrary line, and the exercises are going to make that line earn its place.

▣ Build · stage 5 — one plant in the field, alone
$ go run ./cmd/canopy -mode one -ticks 600 -every 100
canopy: 12x8 valley, 12.00 of daylight on an open cell, 2.50 of water a cell
        crown radius 1.5 covering 9 cells, 0.03 of leaf a gram, 0.04 of height a gram
        one plant at 4,1 and nothing else standing

    tick        mass        lit    height     ceiling
       1      3.9800    12.0000    0.1592    150.0000
       2      6.9004    12.0000    0.2760    150.0000
       3      9.7624    12.0000    0.3905    150.0000
       4     12.5671    12.0000    0.5027    150.0000
     100    130.2397    12.0000    5.2096    150.0000
     200    147.3794    12.0000    5.8952    150.0000
     300    149.6525    12.0000    5.9861    150.0000
     400    149.9539    12.0000    5.9982    150.0000
     500    149.9939    12.0000    5.9998    150.0000
     600    149.9992    12.0000    6.0000    150.0000
  it settles at 149.9992 grams, 6.0000 cells tall, spreading 4.5000 cells of leaf

3.9800, 6.9004, 9.7624, 12.5671. A grid, a sort, a disc walk and a multiplication have been added underneath, and the plant on its own has not moved by a digit: it reads 12.0000 on every tick because there is nothing above it, so the reading and the constant are the same number and the ledger does what it always did. That is the first thing to check after replacing a constant with a computation, and it costs one run.

▣ Build · stage 6 — a seed dropped under something grown
$ go run ./cmd/canopy -mode under -ticks 600 -every 100 -gap 200 -maps
canopy: 12x8 valley, 12.00 of daylight on an open cell, 2.50 of water a cell
        crown radius 1.5 covering 9 cells, 0.03 of leaf a gram, 0.04 of height a gram
        one plant at 4,1 from tick 0, a seedling at 5,1 from tick 200

    tick   tall mass   seed mass   seed lit seed income  seed grown its ceiling
     200    147.3794      2.5067     6.1070      1.5267      1.5067     76.3370
     201    147.4318      3.9828     6.1048      1.5262      1.4761     76.3103
     300    149.6525     65.7406     6.0142      1.5035      0.1926     75.1773
     400    149.9539     73.8191     6.0019      1.5005      0.0246     75.0235
     500    149.9939     74.8496     6.0002      1.5001      0.0031     75.0031
     600    149.9992     74.9809     6.0000      1.5000      0.0004     75.0004
  the tall plant ends at 149.9992 grams and the seedling at 74.9809
      light along row 1:  12.00  12.00  12.00   6.00   4.50   4.50   9.00  12.00  12.00  12.00  12.00  12.00

The seedling germinates into 6.1070 units of light and a ceiling of 76.3370, and both of those figures were set two hundred ticks before it existed, by a plant it will never interact with in any other way. The tall plant's column is the flat one. It reads 12.0000 throughout, its own arithmetic untouched by the arrival of a neighbour, and it finishes on the same 149.9992 grams it reached in the run where it stood alone. Nothing was taken from it. The seedling never had the other half.

The row of light at the bottom is the field itself, printed cell by cell. One cell reads 6.00 where only the tall crown reaches, one reads 9.00 where only the seedling's does, and the two between them, under both, read 4.50, which is 12.00 × 0.50 × 0.75. Compare 4.50 with the 6.0000 the seedling harvested. The ground beneath a plant is darker than the reading the plant took, and the difference is precisely its own leaves.

One row of the light field after the cast, with two crowns over it Twelve cells of row 1 drawn left to right, with the light reaching each of them written inside: 12.00 for the first three, then 6.00, 4.50, 4.50, 9.00, and 12.00 for the last five. Above them, two overlapping bands stand for the two crowns. The higher band belongs to the plant at column 4, covers columns 3, 4 and 5, and lets 0.50 of the light through. The lower band belongs to the seedling at column 5, covers columns 4, 5 and 6, and lets 0.75 through. A gold mark at the top of each band shows where that plant takes its reading: 12.00 above everything for the tall plant, 6.00 for the seedling, which sits below the taller crown and above its own leaves. ROW 1 AFTER THE CAST, WITH TWO CROWNS OVER IT sky: 12.00 lands on every cell 0.50 through the plant at 4,1 reads 12.00 0.75 through the seedling at 5,1 reads 6.00 12.00 12.00 12.00 6.00 4.50 4.50 9.00 12.00 12.00 12.00 12.00 12.00 0 1 2 3 4 5 6 7 8 9 10 11 cells 4 and 5: 12.00 × 0.50 × 0.75 = 4.50 at the ground the seedling reads above its own leaves and below the taller crown gold: the top of a crown, which is where that plant takes its number
Figure 38.1 — the same tick from two sides. Along the ground the field is one number a cell, with the overlap darkest. Up the page it is a stack, and where a plant reads in that stack is the whole of who eats first.

The 40-tick head start

Nothing so far distinguishes the two plants. Same crown radius, same leaf per gram, same height per gram, same thirst, same upkeep, same water under both cells. They are one plant written down twice, and the only fact that differs is which tick each was sown on. Sow them forty ticks apart and run it both ways round.

▣ Build · stage 7 — the same two seeds, in either order
$ go run ./cmd/canopy -mode pair -ticks 600 -gap 40 -maps
canopy: 12x8 valley, 12.00 of daylight on an open cell, 2.50 of water a cell
        crown radius 1.5 covering 9 cells, 0.03 of leaf a gram, 0.04 of height a gram
        the same two seeds 40 ticks apart, 600 ticks of growing

    sown first         west         east            world
          west     149.9992      75.0038 22a2d3097cf49c29
      light along row 1:  12.00  12.00  12.00   6.00   4.50   4.50   9.00  12.00  12.00  12.00  12.00  12.00
          east      75.0038     149.9992 0df6c1dedc228d56
      light along row 1:  12.00  12.00  12.00   9.00   4.50   4.50   6.00  12.00  12.00  12.00  12.00  12.00

Two numbers, 149.9992 and 75.0038, and they change places. The world digest, taken over every cell of the light field and every mass on the grid in row order, differs between the runs, which it must: these are two different valleys, one with the big plant on the west cell and one with it on the east. The rows of light are mirror images of each other about the pair. Forty ticks of a head start, in a model with no rule in it that mentions competition, is the difference between a plant that reaches its cell's ceiling and one that reaches half of it.

Forty ticks turns out to be forty more than the effect needs.

▣ Build · stage 8 — sown on the same tick, settled by one line
$ go run ./cmd/canopy -mode same -ticks 600 -every 150
canopy: 12x8 valley, 12.00 of daylight on an open cell, 2.50 of water a cell
        crown radius 1.5 covering 9 cells, 0.03 of leaf a gram, 0.04 of height a gram
        both seeds sown on tick 0, 600 ticks of growing

    tick         west         east   west lit   east lit
       1       3.9800       3.9700    12.0000    11.9600
       2       6.9004       6.8508    12.0000    11.8408
       3       9.7624       9.6448    12.0000    11.7240
     150     142.8039      82.4405    12.0000     6.2937
     300     149.6525      75.8913    12.0000     6.0142
     450     149.9832      75.0687    12.0000     6.0007
     600     149.9992      75.0046    12.0000     6.0000
  world 68eaeb8944908828

On tick 1 the two are tied at a gram each, the tie-break hands the reading to the west plant, and the east plant loses four hundredths of a unit of light to a seedling's worth of leaf: 3.9800 against 3.9700. That hundredth of a gram is the entire advantage, and by tick 150 it has become 142.8039 against 82.4405. The mechanism is a loop with a positive sign in it. Being slightly bigger makes you slightly taller, which takes slightly more light from your neighbour, which makes your neighbour grow slightly less, which leaves slightly less shade on you. Two identical seeds on identical ground finish 600 ticks twice apart, and one arbitrary line in the caster decided which was which.

None of that is a defect of the model, and none of it is luck. A wood assembles itself out of level ground by exactly this rule. Whatever reaches the light first keeps reaching it, everything arriving later grows up in the remainder, and the two layers a person walking through a forest names as the canopy and the understorey are this rule left running for two hundred years. The same rule says how one kind of plant holds ground it has taken: once that kind is overhead everywhere, a seed of anything else germinates into a ceiling of 75 grams, or 37.50, or less, whatever its own physiology would otherwise have earned it.

Keep the number 37.50 somewhere. When this volume runs a whole valley of these plants for a few thousand ticks and the census falls off a cliff, the first place to look will be here: a rule under which the winners take the light off the losers permanently, in a world where nothing yet dies to make room.

⚠ Worked failure — a seedling that shaded the tree above it

The two halves of the tick are two loops over the same list, and merging them is the obvious tidy-up. One loop, one pass, each plant reads the sky, writes its own shade and spends the reading before the next plant is touched. It is shorter, it allocates nothing, and it visits every stand exactly once.

// cmd/canopy/main.go

// tickOnePass is the tick with its two halves run together: one stand
// at a time reads the sky, writes its own shade into it and spends the
// reading, in the order the world stores them.
func tickOnePass(s *terra.Sky, stands []*terra.Stand) []terra.Ledger {
	s.Clear()
	book := make([]terra.Ledger, len(stands))
	for i, st := range stands {
		st.Lit = s.At(st.At)
		pass := st.Crown.Pass(st.Plant.Mass)
		for _, c := range s.Under(st.At, st.Crown.Crown) {
			s.Dim(c, pass)
		}
		book[i] = st.Plant.Tick(terra.Ground{Light: st.Lit, Water: st.Water})
	}
	return book
}

Run the pair through it and it looks right, which is the problem. So run it twice: the same world, the same sowing, the same six hundred ticks, with the two stands stored in the slice in the two possible orders. The order a program keeps its plants in is book-keeping and can never be physics, so both storages have to produce the same valley.

$ go run ./cmd/canopy -mode order -ticks 600 -gap 40
canopy: 12x8 valley, 12.00 of daylight on an open cell, 2.50 of water a cell
        crown radius 1.5 covering 9 cells, 0.03 of leaf a gram, 0.04 of height a gram
        one plant at 4,1 from tick 0, a seedling at 5,1 from tick 40
        the same world stored both ways round, through both ticks

         tick        stored         west         east   west lit   east lit            world
   two phases    west, east     149.9992      75.0038    12.0000     6.0000 22a2d3097cf49c29
   two phases    east, west     149.9992      75.0038    12.0000     6.0000 22a2d3097cf49c29
     one pass    west, east     149.9992      75.0038    12.0000     6.0000 22a2d3097cf49c29
     one pass    east, west      75.0103     149.9982     6.0001    12.0000 7c173d698723290d

Three rows agree to the last digit and the fourth is a different world. The two-phase tick gives one answer whichever way the stands are stored, as it has to. The one-pass tick agrees with it in one storage order and inverts the entire outcome in the other: the seed that went into the ground forty ticks late finishes at 149.9982 grams, the plant that had the head start finishes at 75.0103, and the tallest thing in the valley is reading 6.0001 units of light. Nothing crashed and no number is impossible. The run is about a different valley from the one it was asked about.

$ go run ./cmd/canopy -mode why -ticks 600 -gap 40
canopy: 12x8 valley, 12.00 of daylight on an open cell, 2.50 of water a cell
        crown radius 1.5 covering 9 cells, 0.03 of leaf a gram, 0.04 of height a gram
        the same world stored both ways, through the one-pass tick
        the numbers first differed on tick 40

  entering tick 40, the plant at 4,1 weighs 82.2353 and stands 3.2894 cells tall
                  the seed at 5,1 weighs 1.0000 and stands 0.0400 cells tall

  one pass, west first:  the plant reads 12.0000 and earns 3.0000
                         the seedling reads  8.7106 and earns 2.1776
  one pass, east first:  the plant reads 11.9600 and earns 2.9900
                         the seedling reads 12.0000 and earns 3.0000
  two phases, either:    the plant reads 12.0000 and earns 3.0000
                         the seedling reads  8.7106 and earns 2.1776

Tick 40 is the tick the seed germinates on, and the disagreement starts there. A plant of 82.2353 grams stands 3.2894 cells tall and carries 2.4671 cells of leaf; a seed of one gram stands 0.0400 cells tall and carries 0.03. When the seed is visited first it reads the untouched sky, 12.0000, and writes its 0.03 of leaf into the cells around it, and the plant eighty times its size then reads 11.9600 and earns 2.9900 instead of 3.0000. Four hundredths of a unit. On tick 40 that is nothing at all, and it is exactly the hundredth of a gram the tie-break was worth two runs ago, fed into the same amplifier for five hundred and sixty ticks.

Merging the loops is not itself the fault. What the merge did was quietly redefine the number being read. In the two-phase tick, a stand's reading means the light that got past the crowns taller than its own, which is a claim about the valley. In the one-pass tick it means the light that got past the crowns of whichever plants sit earlier in a slice, which is a claim about a data structure. Only one of those two sentences has any shade in it.

Carry the general form: when readers and writers of a shared field are interleaved, the order of the loop becomes part of the physics, so either the order is the physics or the loop has to be split. Here the order is part of the physics, and that is exactly why it may not be left to the slice: the caster sorts by height and states its tie-break out loud, because height is the thing that decides who eats first, and index is not. The check that catches it is cheap and belongs in the tree: run the world with its inhabitants stored in a different order and hash both.

The service order

Three ideas came together on this page and each of them turns up again without plants attached.

The first is a field with writers in it. The wind was a field that only ever answered questions, and the soil is a field that gets taken from and holds what is left. The light is a third kind: it is thrown away and recomputed from scratch every tick, from the current state of everything standing in it. That distinction decides how you store it. A store needs conserving, flooring, and a fixed order of service, because a mistake in it accumulates. A recomputed field needs none of the three and can be wiped with impunity, because nothing in it survives a tick. What it needs instead is that every writer has finished before any reader starts.

The second is one-sided competition, which is a different animal from sharing. Two roots in one cell both come away with less than they wanted, and their loss is roughly even. A crown and a seedling do not share anything: the crown takes what it takes at full price and the seedling gets the remainder, and the tall plant's ledger never records that a transaction occurred. Its column of numbers is the same whether the seedling is there or not. Any quantity handed out in that manner amplifies whatever difference it finds, because the reward for being ahead is a larger share of the next round. The runs above fed a hundredth of a gram into that amplifier and took a factor of two out of the far end.

The third is the check. Two things on this page had to be verified rather than believed: that adding a light field did not move the answer for a plant standing alone, and that the order the world stores its plants in does not reach the numbers. Both are identities, both are one run each, and both caught something a correctness test would have passed. The first showed that 3.9800 was still 3.9800 after a grid was slid underneath it. The second showed a valley where a gram of seedling shaded a tree.

The ordering itself generalises past light. Anything a world hands out in a sequence has this structure: whoever is served first sets the conditions for whoever is served second, and if the rule for who is served first is arbitrary then the outcome is arbitrary too. In the light field the rule is height and it is defensible, because being taller is what getting there first physically means. When creatures start feeding, the same question arrives wearing different clothes, and it will want the same answer: state the ordering, justify it, and hash two runs to prove nothing else is deciding.

The understorey run

✓ Checkpoint — light as a field the plants write
  • Given a crown's leaf per gram, its radius and a plant's mass, you can work out the cover on one cell and the share of light that gets through, and check both against the header line the bench prints.
  • compute a seedling's ceiling under one, two and three grown crowns, say why each one halves it, and explain why the plant still reaches its lower ceiling in the same number of ticks.
  • say why shade stacks by multiplying passes instead of adding covers, and what goes wrong at four crowns if it is written the other way.
  • explain what the caster's height sort buys, why a stand reads before its own leaves are written in, and what the tie-break decides when two plants are exactly the same size.
  • Handed two runs of the same sowing with the stands stored in opposite orders, you know which numbers are required to be identical and what it means when they are not.
  • Shown a light row of 12.00, 6.00, 4.50 and 9.00 across four cells, you can name which crowns reach which cell and which plant read which number.
⚡ Exercises — try first, then reveal
Exercise 1 — spread the same leaf wider. A crown of radius 2.5 covers 21 cells instead of 9. Before running anything, work out what 4.50 cells of leaf over 21 cells lets through, then predict whether the late-sown plant does better or worse under it. Check with -crown 2.5.

4.50 ÷ 21 = 0.2143 of cover, so 0.7857 of the light gets past one crown instead of 0.50. The late plant should do considerably better: its ceiling under one grown neighbour is 12.00 × 0.7857 × 0.25 ÷ 0.02, about 118 grams.

$ go run ./cmd/canopy -mode pair -ticks 600 -gap 40 -crown 2.5
canopy: 12x8 valley, 12.00 of daylight on an open cell, 2.50 of water a cell
        crown radius 2.5 covering 21 cells, 0.03 of leaf a gram, 0.04 of height a gram
        the same two seeds 40 ticks apart, 600 ticks of growing

    sown first         west         east            world
          west     149.9992     117.8577 8a171b4a6fd394d3
          east     117.8577     149.9992 2b7332252e6b69da

117.8577 against 75.0038. The wider crown reaches more cells and takes less out of each, so a valley of broad thin canopies is a gentler place to be second than a valley of narrow dense ones. Note what did not change: the winner is still the plant sown first, and it still finishes at exactly its cell's ceiling. Crown width sets how much the loser loses, never who loses.

Exercise 2 — read the number at the ground instead. Change one line so every plant harvests the light left in its own cell after the whole cast, its own leaves included, rather than the reading taken at its crown. Predict what happens to the two identical seeds, then run it with -level.

Reading at ground level makes the competition even-handed: each plant is dimmed by its neighbour whether that neighbour is taller or shorter, and by its own leaves as well. The head start should stop mattering.

$ go run ./cmd/canopy -mode same -ticks 600 -every 150 -level
canopy: 12x8 valley, 12.00 of daylight on an open cell, 2.50 of water a cell
        crown radius 1.5 covering 9 cells, 0.03 of leaf a gram, 0.04 of height a gram
        both seeds sown on tick 0, 600 ticks of growing

    tick         west         east   west lit   east lit
       1       3.9600       3.9600    11.9201    11.9201
       2       6.8022       6.8022    11.6853    11.6853
       3       9.5316       9.5316    11.4620    11.4620
     150      80.0131      80.0131     6.4533     6.4533
     300      80.3829      80.3829     6.4309     6.4309
     450      80.3847      80.3847     6.4308     6.4308
     600      80.3848      80.3848     6.4308     6.4308
  world a8b2eff37c402130
$ go run ./cmd/canopy -mode pair -ticks 600 -gap 40 -level | tail -3
    sown first         west         east            world
          west      80.3852      80.3843 5dae554fe6d93272
          east      80.3843      80.3852 3b9ab687a92ea0bf

Identical, to four places, and the forty-tick head start is worth nine ten-thousandths of a gram. Both plants settle at 80.3848 instead of 149.9992 and 75.0046, because both are now paying for their own leaves and neither can get above the other. One line about where the reading is taken is the difference between a valley with a canopy in it and a valley of equals, and the line is the model, not an implementation detail.

Exercise 3 — find the shade a seed cannot start under. A seed germinates at 1.00 gram. Work out how many grown crowns overhead put its ceiling below that, so it begins shrinking on its first tick, then check with -crowns 8.

The ceiling reaches 1.00 gram when income reaches 0.02, so the light has to fall to 0.02 ÷ 0.25 = 0.08 units. That is a drop by a factor of 12.00 ÷ 0.08 = 150, and halving gets there in eight steps and not seven: seven halvings divide the light by 128, which leaves more than a hundred and fiftieth of it, and eight divide it by 256, which is past the mark.

$ go run ./cmd/canopy -mode shade -crowns 8 | tail -5
        4      0.7500      0.1875      9.3750         8.2643
        5      0.3750      0.0938      4.6875         4.1985
        6      0.1875      0.0469      2.3438         2.1655
        7      0.0938      0.0234      1.1719         1.1491
        8      0.0469      0.0117      0.5859         0.6409

Under seven crowns the ceiling is 1.1719 and the seed creeps up to 1.1491 by tick 100: alive, and never going to be anything. Under eight the ceiling is 0.5859, below the gram it germinated with, so it burns tissue from its first tick and reads 0.6409 at tick 100, lighter than the day it started. Nothing on this page kills it. The ledger keeps subtracting, and the seed spends its whole existence getting smaller on ground it never had a say in landing on.

Every number that decided those runs was the same for both contestants. Same crown radius, same leaf per gram, same height per gram, same thirst, same upkeep, and the winner was chosen by a sowing date and a tie-break. A real valley does not work that way: the plant in the shade is a different plant from the one casting it, built to live on 6.00 units where the other needs 12.00, and paying for that with something else. Those numbers are scattered through three chapters now, some in a grammar and some in a ledger and some in a crown, and they belong in one block that a single species owns.