A Valley That Stays Green
The three repairs
The diagnosis names three failures: growth does not slow when the valley fills, stranded nutrient has no path back to a root, and seeds crossing the rim carry matter out of the world. Measure each repair alone against the collapsed valley, then combine only the repairs whose own column explains what they changed.
The prediction is allowed to be wrong. The density brake was costed at 22 plants, a 2,363.6-gram crop, 1,166 seeds each autumn, and about 30 years before all 528 nutrient units were out of reach. Those numbers came from arithmetic, not a run.
The final run keeps what the measurements earn. It walls the rim, lets stranded nutrient creep back toward roots, and cuts rock cracks where one row can anchor without being erased by the row that wins open soil.
The density brake alone
The coefficient is one line of arithmetic. Count what is standing, divide by the cells a root can live in, take the answer from one. K is read off the bed at the moment the valley is stood up, so a map with more soil in it gets a larger K and nobody edits a constant to make that happen.
// internal/terra/valley.go
// Crowd is the coefficient every income in the valley is multiplied by
// this tick: 1 - Brake*N/K, floored at nothing. N is read off the
// stands and K off the bed, so neither number is a constant somebody
// chose. With no brake set it is exactly one and every multiplication
// by it leaves its number alone.
func (v *Valley) Crowd() float64 {
if v.Brake <= 0 || v.Cells == 0 {
return 1
}
c := 1 - v.Brake*float64(len(v.Stands))/float64(v.Cells)
if c < 0 {
return 0
}
return c
}
// The warmth says how fast the chemistry runs and the crowding says
// what share of it the neighbours leave; both are multipliers on the
// same income, so the tick multiplies them together once and every
// plant is handed the product.
crowd := v.Crowd()
for _, st := range v.Stands {
var l Ledger
if st.Sleep {
l = st.Plant.Sleep(st.Kind.Doze)
} else {
g := v.Bed.Offer(st.At, st.Plant.Root, st.Lit)
l = st.Plant.Grow(g, warm*crowd)
Germination takes the same coefficient. A seed asking whether the ground under it can
cover a seedling's first bill is asking the question a grown plant answers every tick,
and a valley that throttled the grown plants while letting the seedlings start at full
rate would be answering it two ways at once. Both the landing test and the seed bank's
quarterly stir are handed warm*crowd in place of the warmth alone.
$ go run ./cmd/hold -mode brake -years 200 -every 25 -brake 1
hold: the brake and nothing else. seed 5, K = 44 cells a root can live in,
c = 1 - 1.00*N/K multiplied into every income, 200 years, 528.0 units in the ground
year moss herb scrub tree standing in reach stranded gone
n mass n mass n mass n mass grams units units units
1 3 73.0 3 55.5 3 348.0 3 976.8 1453.4 148.356 0.000 0.000
2 1 0.2 2 2.0 18 771.6 6 1435.7 2209.5 87.497 4.137 1.980
3 0 0.0 0 0.0 23 853.3 10 1348.5 2201.8 81.236 11.548 4.200
25 0 0.0 0 0.0 27 124.6 16 233.3 357.9 357.650 73.230 26.820
50 0 0.0 0 0.0 26 119.6 17 247.4 367.0 328.858 92.400 33.480
75 0 0.0 0 0.0 27 316.1 16 640.1 956.1 152.610 136.710 49.050
100 0 0.0 0 0.0 28 131.9 14 204.9 336.8 238.083 165.360 59.760
125 0 0.0 0 0.0 26 230.7 16 442.8 673.5 114.096 207.660 76.380
150 0 0.0 0 0.0 24 447.3 17 639.4 1086.7 1.424 234.810 83.100
175 0 0.0 0 0.0 22 189.4 20 533.8 723.2 6.767 276.900 94.950
200 0 0.0 0 0.0 23 204.8 19 302.8 507.5 1.300 316.230 108.840
what chapter 43 predicted a brake would do, and what it did
predicted measured
plants standing 22 42
grams one of them holds 107.44 12.08
the standing crop, in grams 2363.6 507.5
seeds released a year 1166 63
the share of them that strands 0.492 0.513
nutrient units out of reach a year 17.21 2.13
years the valley's units cover 30.7 248.4
the coefficient at the last tick 0.045, world 1c2ee82130a87de6, census de0c1d2f1c0d5021
Read the standing column first. The braked valley carries 507.5 grams and 42 plants at year 200, and every printed census from year 25 on has at least forty-one plants in it. The same valley with the coefficient left out finished those two centuries holding 1.8 grams and one scrub. Whatever else the brake failed to do, it stopped the collapse.
Now the table underneath, which is what the run was made for. Predicted twenty-two plants, measured forty-two. Predicted 107.44 grams each, measured 12.08. Predicted a standing crop of 2363.6 grams, measured 507.5. Predicted 1,166 seeds a year, measured 63. This is not a prediction that missed by a little.
The assumption the costing flagged as its weakest is the one that survived. A braked seed rain was assumed to strand at the fraction a crashing one stranded at, 0.492, and the braked run strands 0.513 of its rain: two hundredths apart, on a quantity nobody had any right to be confident about. Flagging it was correct and it held anyway.
What broke is a step that was never questioned. N = K/2 is where
N(1 − N/K)M is largest, and that is a fact about the crop, not about
the plants. Nothing in a growth coefficient steers a population to the best point of the
crop curve. A population goes to the count where the plants standing up match the plants
falling over, and here that count is forty-two of forty-four cells: the coefficient at
the last tick reads 0.045, so every plant in the valley is growing at four and a half per
cent of the rate it would manage with the ground to itself. The brake did not pick a
crop. It picked a size of plant, and the plants got small enough to leave every cell
full.
Numbers first. Forty-four cells, and a lone scrub reaching 214.87 grams on open ground. Put twenty-two plants in and the coefficient is one half, so each of them tops out near 107 grams and the crop is about 2,364. Put forty-two in and the coefficient is 0.045, each of them tops out near 10 grams, and the crop is a few hundred. The first number is the biggest crop the arithmetic allows. The second is where the valley actually went.
Two different questions are hiding in that. Write the crop as a function of the count:
C(N) = N · (1 − N/K) · M, largest at N = K/2
That maximum is a property of the formula. Solving it tells you what the ground could carry if something arranged the count for you. Nothing does. The count moves by its own rule: seedlings arrive at some rate that depends on N, plants fall at some other rate that depends on N, and the population sits still where those two are equal.
r(N) = d(N) is the resting count, and it has no reason to be K/2
The two conditions come from different equations and there is no mechanism connecting them. To make a population settle where the crop is largest you have to arrange for the deaths to happen there, and a coefficient on growth arranges nothing of the kind. This is the general lesson under the missed prediction: solving for the best value of a quantity is not the same work as finding out what value the system will take, and only one of those two is answered by running it.
The last two rows of the table are the ones the diagnosis was really asking about, and the verdict in them survives the wrecked arithmetic: a brake on its own is not a cure. The braked valley pushes 2.13 nutrient units a year out of reach against the 17.21 that were costed, so its stock covers 248 years instead of thirty. Eight times longer, and still finite. At year 200 it has 316.230 units stranded on rock and under water, another 108.840 gone over the rim, and 1.300 units left anywhere a root can reach.
Creep and the rim
The soil chapter left a method called Regen in the bed and its own comment
called it the weakest thing on the page: it trickles a fixed amount into every cell up to
a cap, out of nothing at all. Calling it now would end the stranding and end the audit in
the same commit. Every ledger in this volume closes to six decimals because nothing in
the world invents matter, and a valley with a tap in it can no longer tell a leak from a
rate somebody guessed. So the placeholder stays a placeholder, and the repair moves what
is already there.
// internal/terra/bed.go
// Creep moves nutrient sideways: one double-buffered sweep of the grid,
// built the way the water rule is built, where every cell hands the share
// rate of what it is holding to the cells beside it and keeps the rest.
// It is a move and never a gift, so the valley's stock is exactly what
// it was before the sweep, and nutrient that rotted onto bare rock can
// finally reach a root three cells away. It hands back the units that
// changed cells.
func (b *Bed) Creep(rate float64) float64 {
...
for y := 0; y < b.H; y++ {
for x := 0; x < b.W; x++ {
c := sim.Coord{X: x, Y: y}
i := b.index(c)
have := b.cells[i].Nutrient
...
share := rate * have / float64(n)
for k := 0; k < n; k++ {
b.next[side[k]] += share
b.next[i] -= share
moved += share
}
}
}
for i := range b.cells {
b.cells[i].Nutrient = b.next[i]
}
return moved
}
Two details in that loop are the whole of its correctness. Every cell reads from
b.cells and writes into b.next, so a cell's gift does not
depend on whether its neighbour has been visited yet, and the sweep gives the same
answer whichever corner it starts in. And the same share is added to the
neighbour and subtracted from the donor in adjacent statements, so conservation is a
property of how the code is written and not something checked afterwards.
$ go run ./cmd/hold -mode creep -years 120 -brake 1 -wall -rim 1.5
hold: nutrient allowed to move between cells, 120 years, brake b = 1.00
every rate below is the share of a cell's nutrient that leaves it a tick
a tick a year moved in reach stranded standing rows n
0.0000 0.00 0.0 0.331 526.039 606.2 2 42
0.0002 0.72 23991.1 1.905 82.586 2853.0 2 44
0.0010 3.60 132044.7 1.953 80.177 2984.0 2 44
0.0050 18.00 531940.4 2.078 30.545 2954.2 2 44
0.0200 72.00 2331378.6 2.348 8.930 3085.2 2 44
The first line is the valley with no path at all: 526.039 of its 621 nutrient units lying where no root reaches, and 606.2 grams standing. Every line under it has a path, and they are all the same valley. Between 0.0002 and 0.02 a tick the rate spans a hundredfold, and the standing crop across that whole span moves by eight per cent. Having a path is the repair. How fast it runs is a detail, and finding that out cost one sweep of a number that looked like the important knob.
One column reads oddly until you see what it is measuring. The nutrient a root can reach never gets above 2.4 units out of 621 in any run with a path. That is a valley spending its stock as fast as it arrives: 2 units in reach and 3,000 grams standing is what a working circulation looks like from the outside, and the number to watch is the stranded column beside it, which drops from 526 to 80 the moment the sideways sweep is switched on.
The rim wants a decision instead of a mechanism, and there are three honest ones. Let the valley bleed and choose run lengths that admit it. Bring nutrient in from outside, which is the tap again. Or make the rim real: the valley sits in a bowl of rock, and a seed that reaches the wall drops at the foot of it. The third one keeps the world closed and costs a single branch in the landing code, so that is the one this book takes.
if !v.Bed.In(c) {
if !v.Wall {
// It left over the rim. The grams go with it: this is the
// one way matter leaves the valley for good.
v.Seed.Fate[sd.Kind][OffMap]++
v.Blown += s.Seed
v.BlownN += s.Seed * s.Hunger
return
}
// The rim is rock and rock is in the way. The seed stops at the
// foot of it, on the last cell of the valley it was over.
c = v.Bed.Edge(c)
}
Edge clamps a coordinate back onto the grid, so a seed blown out past the
top-left corner lands on the top-left cell. From there it runs the same four tests
every other seed runs, and the rock it has landed on refuses it exactly the way rock
always did.
The wall went in on its own, against the crashing valley, with a simple sum in front of it. Two centuries of that valley put 151.500 nutrient units over the rim, 28.7 per cent of everything it ever owned. Keep those and the valley should last appreciably longer, and the census should show it inside a few decades.
$ go run ./cmd/hold -mode rim -years 200 -brake 0
hold: the rim, four ways, 200 years each, brake b = 0.00
the last column is every census of the run folded into one digest
rim creep standing in reach stranded gone census
open 0.0000 1.8 0.001 376.280 151.500 9d34c222edacd7bb
open 0.0010 345.4 0.251 14.632 438.900 ea9e3c9869de3b1b
walled 0.0000 1.8 0.001 527.780 0.000 9d34c222edacd7bb
walled 0.0010 2556.2 1.431 118.489 0.000 587c166214546d30
Compare the first line with the third. Both finish at 1.8 grams. Both leave 0.001 units within reach of a root. And the last column settles it beyond argument: that digest folds every row of every census of the run into one running hash, so two runs agreeing there agree on 200 years of four rows to the last bit. Sealing the rim changed absolutely nothing.
The reasoning back from that is short once you look at where the units went. In the
open run 151.500 of them left the valley. In the walled run those same seeds stop at
the wall and rot on the rock cells under it, and the stranded column goes up by exactly
what the gone column lost: 376.280 becomes 527.780. Bed.Rot puts the
nutrient into the cell the litter was lying on, and Bed.Roots never looks
at a rock cell. Gone from the world and out of reach are the same condition as far as a
plant is concerned. A boundary is a leak only when the inside can spend what stays
behind it.
Lines two and four are the same pair of runs with the sideways sweep on, and they are the reason the wall is in the finished valley. With the rim open, 438.900 units blow out in two centuries and the crop is down to 345.4 grams. With the rim walled, nothing leaves, the sweep keeps handing the rock's nutrient back to the soil, and the valley is holding 2556.2 grams at year 200 with no brake in it at all.
That last figure deserves to be said out in the open, because it is not what the last two chapters were pointing at. The runaway loop those chapters spent themselves diagnosing does not, by itself, end a valley whose matter cannot escape it. Close the boundary and give the stranded stock a way home and the unbraked valley is still standing after two hundred years. The brake stopped the collapse and did not save the valley; the return path saved the valley without touching the loop. Both of those came out of runs that changed one thing.
Rock-crack refuges
Two rows lost this valley in its first year, and a refuge is the standard answer to that: ground the competition cannot follow a lineage onto, so that whatever kills the open valley does not kill everywhere at once. Two candidates were on the table, the rocky rim and the deep shade. Both can be built and measured, and only one of them turns out to be a refuge here.
// Rim cracks every cell of bare rock that shares a side with soil,
// walked in row order, and hands back how many it made. Which cells
// those are is read off the terrain rather than chosen: the rim of a
// valley is where the rock meets the ground. The slot is how wide those
// cracks are, in cells of root: it is the one number that decides which
// rows the refuge is a refuge from.
func (b *Bed) Rim(hold Cell, slot float64) int {
b.slot = slot
...
}
// Fits reports whether a root of this reach can anchor in this cell.
// Open ground takes any root there is. A crack takes only a root that
// fits inside it, which is a threshold on a trait and not a list of
// species: any row this book ever adds is admitted or refused by the
// same one number.
func (b *Bed) Fits(c sim.Coord, root float64) bool {
if !b.Cracked(c) {
return true
}
return root <= b.slot
}
A cracked cell is rootable ground holding a quarter of what open soil holds, and the
founding draw, the landing test and the seed bank all run through Fits
before anything is allowed to stand in one. At a slot of 1.5 cells the moss at 1.0 and
the herb at 1.5 are admitted; the tree at 2.5 and the scrub at 3.0 are not. Nothing in
that rule mentions a species, so a fifth row added to the table tomorrow is judged by
the same number.
Before the refuge can be judged, the two rows it is for have to be priced. Every row in the table spends part of the year with its leaves down, paying a share of its usual upkeep out of standing tissue and earning nothing, and that is arithmetic anybody can do before running a valley at all.
$ go run ./cmd/hold -mode refuge -years 150 -brake 1 -wall -soak 0.001
hold: a winter priced per row, and then a refuge built in the rock
row cue wake asleep of one gram so needs holds
moss warmth 0.15 911 0.000106 473.5 47.2
herb warmth 0.50 1799 annual - 38.0
scrub daylight 0.80 1411 0.797895 0.2 214.9
tree daylight 1.00 1799 0.798605 0.6 632.8
asleep is ticks of the 3600 with its leaves down; of one gram is what its
dozing upkeep leaves of a gram over those ticks; so needs is the mass it
must go into winter with to come out above its own least mass; holds is
what one of them reaches standing alone on this valley's open ground
one moss founded alone on open ground, nothing else in the valley
tick season warmth awake grams
900 summer 0.854 true 47.1246
1800 autumn 0.854 true 33.2887
2700 winter 0.146 false 2.0692
3600 spring 0.146 false 0.0000
and then it is gone: 0 standing, 1 of its row starved
The moss goes into winter for 911 ticks at half its upkeep, and half of two per cent a tick, compounded 911 times, leaves 0.000106 of a gram. To come out of that above its own least mass of 0.05 grams it would have to go in holding 473.5. One moss with the entire valley to itself, in full daylight, with nothing standing anywhere near it, reaches 47.2. The trace under the table is the same sentence with the model saying it: 47.1246 grams at midsummer, 33.2887 at the turn of autumn, 2.0692 at the turn of winter, nothing at all by spring, and the death register says starved.
So the moss loses this valley to its own dormancy bill, by a factor of ten, on ground no other plant has touched. A place the competition cannot reach is no use to a row whose problem arrives in November.
$ go run ./cmd/hold -mode refuge -years 150 -brake 1 -wall -soak 0.001 (the tables after the trace) the same 150 years with the rim left as rock and cracked two ways. Years is censuses of the 150 with one of that row standing at midsummer and last is the last of those. Seeded is the last year one of that row let go of a seed, whatever the census saw, and cracks counts the stands of it that were in the rim rather than on open ground moss herb rim slot K years last seeded cracks years last seeded cracks rock 0.0 44 2 2 2 0 1 1 3 0 cracked 1.0 75 4 4 4 5 3 3 5 0 cracked 1.5 75 4 4 4 5 21 93 150 65 the scrub and the tree stood at every one of the 150 censuses of all three runs, and neither of them ever stood in a crack: no root that wide fits and at the last census of the cracked run, 31 of the 31 cracks were inside the root disc of something too wide to stand in one the shade at that same census: of the 75 cells a root can live in, the darkest holds 3.900 light and the brightest 13.655, at warmth 0.854 and c = 0.413 row needs light cells too dark moss 0.378 0 herb 4.721 7 scrub 1.888 0 tree 1.133 0
The controlled comparison is the second and third rows of that table. Both have the same 31 cracks and the same K of 75; the only difference between them is the slot, which is the rule about who may anchor. At a slot of 1.0 the herb is refused and its last seed goes into the air in year 5. At 1.5 the herb is admitted, 65 of its stands over the run are in the rim rather than on the valley floor, and it is still letting go of seed in year 150. Same ground, same count of cells, different admission rule. The refuge is doing that, and not the extra soil that came with it.
Two limits in that block are belongs on the page. The refuge leaks: at the last census all 31 cracks were inside the root disc of something too wide to stand in one, so a crack keeps a stem out and lets a root in, and the herb in the rim is drinking from a cell two scrubs are also drinking from. And the deep shade is no refuge whatever. The darkest cell a root can live in holds 3.900 light at that census, and the light a seed needs before it can start there is 0.378 for the moss, 1.133 for the tree, 1.888 for the scrub and 4.721 for the herb. Shade in this valley shuts exactly one row out of exactly seven cells, and that row is the small one. It keeps the scrub out of nowhere at all.
The six-coefficient sweep
One coefficient on one map is a setting somebody liked. The question is which values hold a valley and which do not, and the only way to know the edges of that is to walk across them: three world seeds, six coefficients, every other repair in place.
$ go run ./cmd/hold -mode sweep -years 120 -seeds 3,5,7 -band 0,0.5,1,1.5,2,3 -wall -soak 0.001 -rim 1.5
hold: the density coefficient swept across 120 years, every other repair on
cracked rim at a slot of 1.5, walled, creep 0.0010 a tick
seed K b standing moss herb scrub tree least 20 in reach
3 70 0.00 2042.8 26 4 0 120 2035.4 1.650
3 70 0.50 2111.3 6 4 0 120 2095.7 1.704
3 70 1.00 2119.7 8 5 0 120 2102.7 1.744
3 70 1.50 2028.3 4 6 0 120 2013.9 3.203
3 70 2.00 590.1 4 7 0 120 590.1 304.682
3 70 3.00 209.3 4 3 0 120 209.3 377.241
5 75 0.00 2843.9 4 2 120 120 2823.0 1.808
5 75 0.50 3355.9 4 3 120 120 3340.8 1.945
5 75 1.00 2984.0 4 21 120 120 2966.1 1.953
5 75 1.50 1280.6 3 8 120 120 1269.3 259.095
5 75 2.00 1479.8 3 5 120 120 197.1 206.076
5 75 3.00 545.3 2 4 120 120 543.3 355.775
7 81 0.00 3070.8 2 1 120 120 3022.7 2.032
7 81 0.50 3155.1 2 1 120 120 3108.6 2.119
7 81 1.00 3128.1 2 2 120 120 3128.1 2.507
7 81 1.50 1102.5 2 3 120 120 748.9 389.296
7 81 2.00 297.1 2 3 120 120 282.4 563.262
7 81 3.00 589.5 2 2 118 120 568.6 485.736
the four row columns are years of the 120 with one of that row standing,
and least 20 is the smallest standing crop of the last twenty years
The band is wide at the bottom and has an edge at the top. From 0 to 1 all three worlds finish standing, with between 2042.8 and 3355.9 grams of crop and their smallest year of the last twenty within a few per cent of the ending. At 1.5 two of the three have turned: seed 5 drops to 1280.6 grams and seed 7 to 1102.5, and the last column on those two rows goes from about two units to 259.095 and 389.296. Seed 3 holds at 1.5 and goes at 2, and by 2 every world has lost half its crop or more.
That last column is the tell, and it says something the standing column alone does not. A valley with 563.262 nutrient units within reach of a root and 297.1 grams standing on it is starving on a full larder, which is where the crashing valley ended up from the opposite direction. Too much brake empties a valley about as thoroughly as none at all, and the only difference is which column tells you first.
The valley this book keeps runs at b = 1, the coefficient the diagnosis derived. The sweep puts it at the top edge of the band, and the choice costs something measurable: on seed 5 it holds 2984.0 grams against 3355.9 at b = 0.5. What it buys is the herb, which appears at 21 censuses of the 120 at b = 1 and 3 at b = 0.5. A coefficient that leaves a little more room decides which rows can use it.
$ go run ./cmd/hold -mode long -years 1000 -every 100 -brake 1 -wall -soak 0.001 -rim 1.5 -shot assets/frames/valley-year-1000.png
hold: 1000 years of the repaired valley, seed 5
brake b = 1.00 on K = 75, creep 0.0010 a tick, rim walled and cracked
31 cracks at a slot of 1.5, 621.0 nutrient units in the ground
year moss herb scrub tree standing in reach stranded gone
n mass n mass n mass n mass grams units units units
1 3 117.4 3 98.8 1 192.0 1 375.6 783.7 310.928 19.836 0.000
2 2 0.5 12 12.0 8 313.7 2 1102.4 1428.7 243.495 72.144 0.000
3 1 0.2 12 12.0 22 1316.0 3 1055.3 2383.5 109.414 85.667 0.000
100 0 0.0 0 0.0 37 1918.7 7 1101.3 3020.0 1.966 83.347 0.000
200 0 0.0 0 0.0 32 1561.6 10 1301.6 2863.2 1.880 76.737 0.000
300 0 0.0 0 0.0 34 1596.4 10 1303.6 2900.0 1.917 70.061 0.000
400 0 0.0 0 0.0 32 1343.3 12 1457.6 2800.9 1.894 62.918 0.000
500 0 0.0 0 0.0 29 1118.9 15 1572.0 2690.8 1.881 57.353 0.000
600 0 0.0 0 0.0 28 914.2 16 1666.7 2580.9 1.837 56.871 0.000
700 0 0.0 0 0.0 23 702.6 20 1803.2 2505.8 1.845 45.760 0.000
800 0 0.0 0 0.0 29 1015.1 15 1622.5 2637.5 1.865 53.660 0.000
900 0 0.0 0 0.0 27 984.1 16 1653.9 2638.0 1.885 51.969 0.000
1000 0 0.0 0 0.0 27 943.9 17 1718.3 2662.2 1.906 46.544 0.000
every row over the whole run. Seen is censuses with one of that row
standing at midsummer; the two last columns are the last year one of
them let go of a seed and the last year one of them stood up at all
peak n in year seen released grew seeded sprouted
moss 3 1 4 6 5 4 4
herb 12 2 21 23227 11614 1000 1000
scrub 38 9 1000 431215 520 1000 1000
tree 22 677 1000 56431 62 1000 986
The census stops moving in any direction after the first century. Year 100 reads 3020.0 grams and year 1000 reads 2662.2, and everything between wanders inside that. What does move is the mix: 37 scrubs and 7 trees at year 100, 27 scrubs and 17 trees at year 1000, trading slowly back and forth on the way. No line of code arranges that. It is what two rows do on ground where both of them can pay their bills, and the run that crashed lost its last tree in year 86.
The row table underneath is where the census gets caught out. The moss put its last seed into the air in year 4, and that lineage is finished. The herb was last seen standing at a midsummer census in year 93, and it released a seed in year 1000 and put a seedling up in year 1000. It has been there the whole time. A herb is an annual whose leaves come up on warmth, and the warm half of this calendar runs from mid-spring to mid-autumn: one that comes up after the midsummer tick lives out its season and is gone before the next one, so the census can only catch the few that sprouted in the weeks before it. 11,614 herbs stood up over the run and one reading a year saw twenty-one years of them. A row a census calls extinct can be seeding nine centuries later.
$ go run ./cmd/hold -mode long -years 1000 ... (the tables after the census) the valley at year 1000, cell by cell, in two columns ############ #ststtsstss# #sssstststs# #s~~~~~stss# #s~~~~~~sss# #ttt~~~~stt# #ttt~ssssts# ############ cell row grams light in reach cell row grams light in reach 1,1 scrub 84.238 12.374 0.3067 8,3 tree 262.359 4.135 0.6986 2,1 tree 56.042 10.054 0.3151 9,3 scrub 19.970 3.468 0.8585 3,1 scrub 28.204 6.667 0.5454 10,3 scrub 22.532 5.262 0.7182 4,1 tree 97.279 7.595 0.4650 1,4 scrub 20.730 8.823 0.5117 5,1 tree 178.125 6.644 0.5004 8,4 scrub 21.592 3.696 0.9223 6,1 scrub 36.213 6.082 0.7698 9,4 scrub 26.348 4.431 0.8256 7,1 scrub 26.419 4.425 0.8119 10,4 scrub 28.884 4.951 0.6956 8,1 tree 75.701 5.851 0.5408 1,5 tree 111.451 9.149 0.3966 9,1 scrub 8.547 6.334 0.5558 2,5 tree 25.329 9.149 0.3972 10,1 scrub 62.889 13.175 0.4174 3,5 tree 151.871 9.430 0.3812 1,2 scrub 76.929 12.068 0.3344 8,5 scrub 29.235 4.628 0.6747 2,2 scrub 37.953 9.806 0.4348 9,5 tree 188.486 4.800 0.6216 3,2 scrub 35.815 6.468 0.6670 10,5 tree 101.790 8.295 0.4086 4,2 scrub 47.365 7.595 0.6052 1,6 tree 34.752 9.430 0.3800 5,2 tree 96.239 6.413 0.5180 2,6 tree 50.353 9.430 0.3800 6,2 scrub 22.463 3.806 0.8134 3,6 tree 70.372 9.669 0.3678 7,2 tree 144.827 4.209 0.7093 5,6 scrub 0.500 12.161 0.3004 8,2 scrub 13.523 5.373 0.6893 6,6 scrub 23.302 15.701 0.2180 9,2 tree 31.786 5.571 0.5648 7,6 scrub 63.262 10.516 0.3539 10,2 scrub 48.834 7.711 0.6245 8,6 scrub 48.399 8.129 0.6228 1,3 scrub 24.927 10.121 0.4837 9,6 tree 41.561 8.074 0.4154 7,3 scrub 29.159 5.331 0.9217 10,6 scrub 55.664 8.962 0.4296 the books in nutrient units the valley opened with 621.000000 in living tissue 314.382078 in the air 0.000000 in the bank 43.040000 lying dead 215.127886 in the ground 48.450035 blown away 0.000000 difference 0.000000 2662.2 grams standing, 44 plants, 2 rows at this census, 1.906 units in reach 510879 seeds released, 12201 germinated, 646 waiting in the ground world df1c117e69bdf157, census 75c77a67f6f430bb after 3600000 ticks 3600000 ticks in 3m22.21s, 17803 ticks a second (measured here; yours will differ) frame b337de56fa55727da760f7da00aea5215a703e3984129b05d42b517ce7e955b6, wrote assets/frames/valley-year-1000.png
Count the letters on the map and there are 44 of them on 44 cells of soil: at year 1000 every cell of open ground in this valley has something standing on it. None of the 31 cracks does, at that instant. The cell table beside it is the census the volume has been working towards, one line per plant, its grams, the light on its cell and the nutrient its roots can reach, and all of it replays from the world seed.
The books close at 0.000000 across 3.6 million ticks with a sideways sweep running in every one of them, and the blown-away line reads 0.000000: in a thousand years nothing at all left this valley. The run cost 3m22.21s here, at 17,803 ticks a second against the eighty-odd thousand the same module manages on the valley that died. A world with 44 plants in it costs four or five times as much per tick as an empty one, and that is the bill for the repair: every one of those plants reads a root disc and writes a crown into the light field, every tick, for ever. Your machine will give you a different pair of numbers and the same ratio.
Every command printed above is run again when this volume's contract is checked:
podman starts a container on a pinned Go image, copies the module into it, runs the
commands in order and diffs the output against the committed transcript. Anything that
drifts by a digit fails there. One line on this page cannot be replayed and says so
where it is printed: the ticks a second, which measures a machine and not a valley.
man podman-run if you want to see what the bench is doing.
assets/frames/valley-year-1000.png: the valley at the last midsummer of a
thousand-year run, drawn by the painter the seeding chapter wrote. Forty-four plants on
forty-four cells of soil, twenty-seven scrubs and seventeen trees, and 0.000 nutrient
units lost over the rim since the world opened.
Measured repairs
The method under this page is older than any of the mechanisms in it. Change one thing. Run the whole system. Print the result beside the number you expected before you ran it. Every finding above came out of that, and two of them contradicted the number written down before the run, which is the only sign that the discipline is doing any work.
A costed prediction is write down exactly because it can fail in a locatable place. The costing that opened this page was wrong by a factor of eight on the number it cared about, and because every step of it was printed it took one run to see that the flagged assumption held and an unflagged one broke. A prediction written as a sentence would have been wrong in an unlocatable way. Write the intermediate quantities down and a wrong answer tells you which step to fix.
Two of the repairs generalize past valleys. The first is what the rim taught: a boundary is a leak only when what stays behind it can be used, so before sealing anything, ask what the inside can spend. Sealing this one moved 151.500 units from a column called gone to a column called stranded and produced a byte-identical census. The second is what the refuge taught: a place a competitor cannot follow you to is an answer to being excluded from places, and the moss was being excluded from a season. Diagnose the exclusion before building the refuge, or you will build a good mechanism for the wrong row.
The instrument that made the rim finding legible is copy too. Folding every census of a run into one hash turns two hundred tables into sixteen characters, and two runs that agree there agree everywhere, including on the years nobody printed. Sampling cost this volume a worked failure twice over: a seed pool sized from a column that was structurally zero, and a row declared extinct in year 93 that was still seeding in year 1000. Both were readings taken at the wrong moment. A digest over everything is cheap insurance against the moment you chose.
- write a density coefficient whose K is read off the map instead of typed into the source, and say why the same coefficient has to reach the germination test as well as the growth of a standing plant.
- Given a costed prediction that came out wrong, you can put its steps in a column beside the measured ones and name which step failed, and you can tell a wrong assumption from a wrong piece of arithmetic.
- say why the count that makes a crop largest and the count a population settles at are answers to different questions, and what a growth coefficient does not decide.
- move a conserved quantity between cells with a double-buffered sweep that gives and takes in the same statement, and argue why that beats topping every cell up from nothing.
- test whether a boundary is really a leak by asking what the inside can spend, and fold a run's whole census into one digest so two runs cannot appear to agree by accident.
- Handed a row that is dying out, you can price its dormancy against its ceiling before building anything, and tell an exclusion in space from an exclusion in the calendar.
Exercise 1 — take the wall down. The finished valley has all
three repairs. Predict what three centuries of it look like with the rim open again,
then run go run ./cmd/hold -mode long -years 300 -every 100 -brake 1 -soak
0.001 -rim 1.5 and read the books at the foot of it.
The blown-away line reads 586.270 of the valley's 621 nutrient units, and what is left standing at year 300 is 199.1 grams on 22 plants, with 0.120 units anywhere a root can reach. The sideways sweep is still running and the brake is still on; they are recirculating a stock that is nearly all gone.
The interesting part is the rate. The bleed averages under two units a year and it takes the valley apart anyway, because there is no arrow bringing any back. A loss that is invisible in a decade decides a millennium, and the only defence is a ledger long enough to show it.
Exercise 2 — brake too hard. The sweep says the band ends
somewhere between 1 and 1.5. Predict what a coefficient of 2 does to a valley with
every other repair in place, then run go run ./cmd/hold -mode sweep -seeds 5
-band 2 -years 120 -wall -soak 0.001 -rim 1.5.
It finishes at 1479.8 grams, which does not look like a failure until the two columns beside it are read: the smallest crop of the last twenty years is 197.1 grams, so that valley is swinging by a factor of seven, and 206.076 nutrient units are sitting within reach of a root that cannot grow fast enough to spend them.
A brake strong enough to hold the count down also holds the plants below the size at which they can use what they are standing on. The failure looks nothing like the crash and ends in the same place, and the column that tells you which one you are in is the nutrient a root can reach.
Exercise 3 — give the moss twice the sky. The moss needs
473.5 grams to survive a winter and reaches 47.2. Predict whether doubling the
daylight closes that gap, then run go run ./cmd/hold -mode refuge -years 5
-light 24 and read the first table and the trace.
What it holds doubles to 94.3 grams and what it needs does not move at all: 473.5, the same figure. The trace shows the same death on the same schedule, 94.2493 grams at midsummer, 4.1384 at the turn of winter, gone by spring.
The needs column is upkeep, dormancy length and least mass multiplied together, and daylight is in none of the three. Doubling an input that only appears on one side of a comparison cannot change which side wins, and the arithmetic said so before the run did.
That is the volume. A plant was a budget kept once a tick, then the ground under it got two numbers a root could spend, then water arrived somewhere in particular and ran downhill, then the crowns started taking the light before anything under them could read it. Four of those budgets became four rows of a table, a year turned growth into something the calendar does to a plant, and seeds put the next generation on the wind. Two hundred unpaced years killed all of it, the loops went on paper, and three repairs came off that diagram and were built one at a time. What runs now is a valley that feeds itself for a thousand years and closes its books to six decimals. It has one thing nothing else in the world has yet: a standing crop, sitting there, paid for and unclaimed. The next thing to arrive will eat it, and a creature turns out to keep the same four columns a plant keeps, read from the other side of the meal: what it takes in, what standing still costs it, what moving costs on top of that, and what is left over to grow on.