feat(samples): add "World on a warming planet" capstone
A miniature of the Club of Rome's World3 with its pollution sector reframed as a climate channel: four coupled Stocks where Reinforcing capital and population overshoot a finite Resource and the carbon they burn locks in the warming that finishes them. A qualitative tribute to the overshoot-and-collapse shape on the existing four rules, not a port of the equations.
This commit is contained in:
@@ -64,6 +64,21 @@
|
|||||||
* threshold the smooth rules can't draw — so here the
|
* threshold the smooth rules can't draw — so here the
|
||||||
* ceiling is *declared*, not emergent.
|
* ceiling is *declared*, not emergent.
|
||||||
*
|
*
|
||||||
|
* And the capstone — every piece at once, at the scale the whole gallery points toward:
|
||||||
|
*
|
||||||
|
* 16. World on a warming planet — the Club of Rome's World3 (Meadows et al., *The Limits
|
||||||
|
* to Growth*) in miniature, its pollution sector reframed
|
||||||
|
* as a climate channel: four coupled Stocks where two
|
||||||
|
* Reinforcing engines (capital, population) overshoot a
|
||||||
|
* finite Resource and the carbon they burn locks in the
|
||||||
|
* warming that finishes them. The gallery's largest model,
|
||||||
|
* it composes the whole vocabulary — a Stock→Stock bridge
|
||||||
|
* ("Epidemic"), logistic-style limits ("Limits to growth"),
|
||||||
|
* a renewable-resource overshoot ("Overshoot and collapse"),
|
||||||
|
* and the bathtub's slow sink ("Bathtub with an overflow")
|
||||||
|
* — into one system. A qualitative tribute to the shape, not
|
||||||
|
* a port of the equations.
|
||||||
|
*
|
||||||
* These are plain data built from the same tested constructors the store uses
|
* These are plain data built from the same tested constructors the store uses
|
||||||
* (factory.ts), so every sample is a valid Model by construction. `build()`
|
* (factory.ts), so every sample is a valid Model by construction. `build()`
|
||||||
* mints fresh ids on each call, so loading a sample twice never collides.
|
* mints fresh ids on each call, so loading a sample twice never collides.
|
||||||
@@ -1425,6 +1440,256 @@ function bathtubOverflow(): Model {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* World on a warming planet — the gallery's capstone: the Club of Rome's World3
|
||||||
|
* (Donella Meadows et al., *The Limits to Growth*, 1972) in miniature, with its
|
||||||
|
* persistent-pollution sector reframed as the climate channel. It is a *qualitative
|
||||||
|
* tribute*, not a port: World3 runs on hundreds of equations, table-function
|
||||||
|
* nonlinearities, and delays; here four Stocks on the gallery's four rules
|
||||||
|
* reproduce the famous *shape* — overshoot and collapse — without the apparatus.
|
||||||
|
*
|
||||||
|
* The four sectors and how they couple:
|
||||||
|
* - **Capital** is the growth engine. output = factor × Capital × availability;
|
||||||
|
* a slice is reinvested (investment, an inflow) so Capital → output → investment
|
||||||
|
* → Capital is **Reinforcing** — the economy compounds. depreciation drains it
|
||||||
|
* (**Balancing**).
|
||||||
|
* - **Resources** is the finite planet: a nonrenewable Stock with *no* inflow.
|
||||||
|
* `availability` ∝ Resources scales output, so as the reserve runs down the
|
||||||
|
* economy is choked: Resources → availability → output → combustion → Resources,
|
||||||
|
* one `−` → **Balancing**. This is the World3 limit.
|
||||||
|
* - **Atmospheric carbon** is the climate channel. The same act that runs the
|
||||||
|
* economy loads the sky: `combustion` is a single Stock→Stock Flow that drains
|
||||||
|
* Resources straight *into* carbon — burning the reserve *is* the emission. A
|
||||||
|
* slow `removal` (a Gap toward preindustrial) is the sink; tuned slow, so the
|
||||||
|
* carbon **stays** — the bathtub, the lesson that stabilising emissions ≠
|
||||||
|
* stabilising concentration.
|
||||||
|
* - **Population** grows on births (**Reinforcing**) against baseline deaths
|
||||||
|
* (**Balancing**). `warming` (a Gap reading carbon above the same preindustrial
|
||||||
|
* baseline — the constant does double duty) drives `heat deaths`, the climate's
|
||||||
|
* bite on people, and `climate damage`, its bite on Capital. That damage closes
|
||||||
|
* the long cross-sector loop output → combustion → carbon → warming → climate
|
||||||
|
* damage → Capital — so the climate is genuine *feedback*, not one-way forcing.
|
||||||
|
*
|
||||||
|
* Note what is *absent*: there is no Reinforcing tipping loop. The climate here is
|
||||||
|
* slow accumulation braked by its sink, not a runaway — faithful to World3's
|
||||||
|
* pollution sector, and a different model from a carbon-cycle one built to tip.
|
||||||
|
* The Reinforcing engines are growth (Capital) and births (Population); everything
|
||||||
|
* the planet pushes back with is Balancing. That asymmetry *is* limits-to-growth.
|
||||||
|
*
|
||||||
|
* What emerges (start 0, stop 250, dt 1): the economy booms, then overshoots the
|
||||||
|
* reserve and collapses — output peaks ≈540 at t≈47, Capital ≈1040 at t≈60, both
|
||||||
|
* falling to near nothing as Resources deplete 1000 → ~120. The carbon they burned
|
||||||
|
* climbs to ~1085 ppm and *holds* near 1000 (warming locked at ~4 °C) long after
|
||||||
|
* the emissions stop. That locked-in heat is what finishes Population: it overshoots
|
||||||
|
* to ~310 at t≈60 — the *last* sector to peak — then collapses to ~30, a third of
|
||||||
|
* where it began. Growth first, people last; the limit and the heat together.
|
||||||
|
*/
|
||||||
|
function worldOnAWarmingPlanet(): Model {
|
||||||
|
// Economy upper-left (Resources, Capital and their converters/flows); the carbon
|
||||||
|
// Stock sits centre on the same baseline as Resources, joined by the combustion
|
||||||
|
// bridge; `warming` hangs below it and radiates harm out to *both* the economy
|
||||||
|
// (up-left, climate damage) and Population (right, heat deaths). Population runs
|
||||||
|
// its own birth/death column on the right. Valves are hand-placed, not at
|
||||||
|
// midpoints, to keep every Information Link in open space.
|
||||||
|
|
||||||
|
// Resources sector (far left): a finite reserve that only depletes; its grade
|
||||||
|
// (availability) is what the economy can actually draw on.
|
||||||
|
const resources = makeStock({ x: -820, y: 60 }, "Resources")
|
||||||
|
resources.initialValue = 1000
|
||||||
|
resources.description =
|
||||||
|
"The planet's nonrenewable reserve — it has no inflow, so it only ever falls. As it runs down it chokes the economy that lives off it."
|
||||||
|
const availability = makeConverter({ x: -820, y: -140 }, "availability")
|
||||||
|
// availability = 0.001 × Resources: a 0…1 grade (starts at 1.0) that scales output.
|
||||||
|
availability.rule = { kind: "proportional", factor: 0.001 }
|
||||||
|
availability.description =
|
||||||
|
"How much of the reserve is still cheap to reach (∝ Resources, ~1.0 at the start, → 0 as it empties). It is the brake the finite planet puts on output."
|
||||||
|
|
||||||
|
// Economy: Capital is the hub — investment fills it (Reinforcing), depreciation
|
||||||
|
// and climate damage drain it (Balancing). output relays Capital × availability.
|
||||||
|
const capital = makeStock({ x: -420, y: -40 }, "Capital")
|
||||||
|
capital.initialValue = 50
|
||||||
|
capital.description =
|
||||||
|
"Industrial capital — the engine that compounds by reinvesting its own output, until the reserve it burns runs short and the heat it raises bites back."
|
||||||
|
const output = makeConverter({ x: -600, y: -200 }, "output")
|
||||||
|
// output = Capital × availability (both `+`): the economy's activity, throttled by
|
||||||
|
// how much reserve is left. It feeds investment, combustion, and (via carbon) warming.
|
||||||
|
output.rule = { kind: "proportional", factor: 1 }
|
||||||
|
output.description =
|
||||||
|
"Industrial output, Capital × availability: more capital makes more, but a depleting reserve scales it down. The relay that ties the economy to the planet."
|
||||||
|
const investSrc = makeCloud({ x: -680, y: -40 })
|
||||||
|
const investment = makeFlow({ x: -550, y: -40 }, "investment", investSrc.id, capital.id)
|
||||||
|
// investment = 12% of output, reinvested: Capital → output → investment → Capital,
|
||||||
|
// no `−` → the Reinforcing engine that drives the boom.
|
||||||
|
investment.rule = { kind: "proportional", factor: 0.12 }
|
||||||
|
investment.description =
|
||||||
|
"Output ploughed back into capital, 12% per step. More capital → more output → more investment: the Reinforcing engine of growth."
|
||||||
|
const deprSink = makeCloud({ x: -240, y: -200 })
|
||||||
|
const depreciation = makeFlow({ x: -330, y: -120 }, "depreciation", capital.id, deprSink.id)
|
||||||
|
// depreciation = 4% of Capital: wear. Below investment while the reserve lasts,
|
||||||
|
// above it once availability collapses — which is what tips Capital into decline.
|
||||||
|
depreciation.rule = { kind: "proportional", factor: 0.04 }
|
||||||
|
depreciation.description =
|
||||||
|
"Capital wearing out, 4% per step — the Balancing drain that overtakes investment once a depleted reserve starves output."
|
||||||
|
const dmgSink = makeCloud({ x: -640, y: -260 })
|
||||||
|
const climateDamage = makeFlow({ x: -540, y: -160 }, "climate damage", capital.id, dmgSink.id)
|
||||||
|
// climate damage = 0.004 × Capital × warming: the heat's bite on the economy. It
|
||||||
|
// closes the long climate→economy loop, so warming is feedback, not just forcing.
|
||||||
|
climateDamage.rule = { kind: "proportional", factor: 0.004 }
|
||||||
|
climateDamage.description =
|
||||||
|
"Capital lost to a hotter world, ∝ Capital × warming. It closes the loop from output through carbon and warming back onto Capital — the climate biting the economy that warmed it."
|
||||||
|
|
||||||
|
// The combustion bridge: a single Stock→Stock Flow draining Resources *into*
|
||||||
|
// carbon. Burning the reserve is the emission — no Source, no Sink, both ends Stocks.
|
||||||
|
const carbon = makeStock({ x: 40, y: 60 }, "Atmospheric carbon")
|
||||||
|
carbon.initialValue = 280
|
||||||
|
carbon.unit = "ppm"
|
||||||
|
carbon.description =
|
||||||
|
"Carbon in the air (ppm), 280 at the preindustrial start. Combustion fills it fast, the sink empties it slow — so it climbs, then stays. The bathtub."
|
||||||
|
const combustion = makeFlow({ x: -380, y: 60 }, "combustion", resources.id, carbon.id)
|
||||||
|
// combustion = 3% of output: the reserve burned each step, drained from Resources
|
||||||
|
// and added to carbon in one move. Conserved — what leaves the ground enters the sky.
|
||||||
|
combustion.rule = { kind: "proportional", factor: 0.03 }
|
||||||
|
combustion.description =
|
||||||
|
"The reserve burned to run the economy, ∝ output — drained from Resources straight into the air. The same act depletes the planet and loads the sky."
|
||||||
|
const removalSink = makeCloud({ x: 40, y: -140 })
|
||||||
|
const removal = makeFlow({ x: 40, y: -40 }, "removal", carbon.id, removalSink.id)
|
||||||
|
// removal = 0.001 × (carbon − preindustrial): the natural sink, a Gap toward the
|
||||||
|
// baseline. Deliberately slow, so carbon barely recedes — the locked-in warming.
|
||||||
|
removal.rule = { kind: "gap", factor: 0.001 }
|
||||||
|
removal.description =
|
||||||
|
"Nature drawing carbon back toward the preindustrial baseline — a slow Gap. So slow that once emitted, the carbon stays for the run: warming you cannot take back."
|
||||||
|
const preindustrial = makeConverter({ x: -180, y: 240 }, "preindustrial")
|
||||||
|
preindustrial.rule = { kind: "constant", value: 280 }
|
||||||
|
preindustrial.description =
|
||||||
|
"The preindustrial carbon baseline (280 ppm), doing double duty: the target the sink draws toward and the zero from which warming is measured."
|
||||||
|
|
||||||
|
const warming = makeConverter({ x: 300, y: 200 }, "warming")
|
||||||
|
// warming = 0.005 × (carbon − preindustrial): °C above preindustrial. A Gap reading
|
||||||
|
// carbon as the level and the baseline as the target; ~4 °C at the carbon peak.
|
||||||
|
warming.rule = { kind: "gap", factor: 0.005 }
|
||||||
|
warming.description =
|
||||||
|
"Warming in °C above preindustrial, ∝ (carbon − baseline) — about 4 °C at the peak. It feeds both the deaths it causes and the capital it destroys."
|
||||||
|
|
||||||
|
// Population: births compound it (Reinforcing), baseline deaths brake it (Balancing),
|
||||||
|
// and heat deaths add the climate toll that eventually overwhelms the birth engine.
|
||||||
|
const population = makeStock({ x: 660, y: 60 }, "Population")
|
||||||
|
population.initialValue = 100
|
||||||
|
population.unit = "people"
|
||||||
|
population.description =
|
||||||
|
"The people — grows on births, thinned by ordinary deaths, and finally overwhelmed by the heat the economy's carbon locked in. The last sector to peak, and to fall."
|
||||||
|
const birthSrc = makeCloud({ x: 660, y: -140 })
|
||||||
|
const births = makeFlow({ x: 660, y: -40 }, "births", birthSrc.id, population.id)
|
||||||
|
// births = 5% of Population: the Reinforcing engine, +3%/step net of baseline deaths.
|
||||||
|
births.rule = { kind: "proportional", factor: 0.05 }
|
||||||
|
births.description =
|
||||||
|
"New people, 5% of the population each step — the Reinforcing engine that grows it while the world stays cool enough to bear it."
|
||||||
|
const deathSink = makeCloud({ x: 520, y: 240 })
|
||||||
|
const deaths = makeFlow({ x: 600, y: 160 }, "deaths", population.id, deathSink.id)
|
||||||
|
// deaths = 2% of Population: ordinary mortality, the Balancing drain births outrun early.
|
||||||
|
deaths.rule = { kind: "proportional", factor: 0.02 }
|
||||||
|
deaths.description =
|
||||||
|
"Ordinary deaths, 2% of the population each step — the Balancing drain the birth engine outpaces, until the heat tips the balance."
|
||||||
|
const heatSink = makeCloud({ x: 820, y: 240 })
|
||||||
|
const heatDeaths = makeFlow({ x: 740, y: 160 }, "heat deaths", population.id, heatSink.id)
|
||||||
|
// heat deaths = 0.011 × Population × warming: the climate toll. Once warming passes
|
||||||
|
// ≈2.7 °C this outflow overtakes net births and Population turns from boom to collapse.
|
||||||
|
heatDeaths.rule = { kind: "proportional", factor: 0.011 }
|
||||||
|
heatDeaths.description =
|
||||||
|
"Deaths from a hotter world, ∝ Population × warming. Once warming passes ≈2.7 °C this overtakes net births, and the population collapses with the heat that never lifts."
|
||||||
|
|
||||||
|
return model(
|
||||||
|
"World on a warming planet",
|
||||||
|
[
|
||||||
|
resources,
|
||||||
|
availability,
|
||||||
|
capital,
|
||||||
|
output,
|
||||||
|
investSrc,
|
||||||
|
investment,
|
||||||
|
deprSink,
|
||||||
|
depreciation,
|
||||||
|
dmgSink,
|
||||||
|
climateDamage,
|
||||||
|
carbon,
|
||||||
|
combustion,
|
||||||
|
removalSink,
|
||||||
|
removal,
|
||||||
|
preindustrial,
|
||||||
|
warming,
|
||||||
|
population,
|
||||||
|
birthSrc,
|
||||||
|
births,
|
||||||
|
deathSink,
|
||||||
|
deaths,
|
||||||
|
heatSink,
|
||||||
|
heatDeaths,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
link(resources, availability, "+", "More reserve left → more of it cheap to reach."),
|
||||||
|
link(capital, output, "+", "More capital → more output: the level the economy compounds on."),
|
||||||
|
link(
|
||||||
|
availability,
|
||||||
|
output,
|
||||||
|
"+",
|
||||||
|
"More availability → more output. With Capital, output = Capital × availability — the planet throttling the economy.",
|
||||||
|
),
|
||||||
|
link(
|
||||||
|
output,
|
||||||
|
investment,
|
||||||
|
"+",
|
||||||
|
"More output → more reinvested: the + that makes the growth loop Reinforcing.",
|
||||||
|
),
|
||||||
|
link(capital, depreciation, "+", "More capital → more wearing out each step."),
|
||||||
|
link(capital, climateDamage, "+", "More capital → more of it exposed to a hotter world."),
|
||||||
|
link(
|
||||||
|
warming,
|
||||||
|
climateDamage,
|
||||||
|
"+",
|
||||||
|
"More warming → more capital destroyed. This − outflow closes the climate→economy loop: feedback, not forcing.",
|
||||||
|
),
|
||||||
|
link(
|
||||||
|
output,
|
||||||
|
combustion,
|
||||||
|
"+",
|
||||||
|
"More output → more reserve burned: economic activity is what emits.",
|
||||||
|
),
|
||||||
|
link(carbon, removal, "+", "More carbon above the baseline → faster (but slow) removal."),
|
||||||
|
link(
|
||||||
|
preindustrial,
|
||||||
|
removal,
|
||||||
|
"-",
|
||||||
|
"The baseline the sink draws toward (the − target): removal stops once carbon is back to it.",
|
||||||
|
),
|
||||||
|
link(carbon, warming, "+", "More carbon → more warming: carbon is the level in the gap."),
|
||||||
|
link(
|
||||||
|
preindustrial,
|
||||||
|
warming,
|
||||||
|
"-",
|
||||||
|
"The baseline warming is measured from (the − target): no carbon above it, no warming.",
|
||||||
|
),
|
||||||
|
link(
|
||||||
|
population,
|
||||||
|
births,
|
||||||
|
"+",
|
||||||
|
"More people → more births: the + that makes the population loop Reinforcing.",
|
||||||
|
),
|
||||||
|
link(population, deaths, "+", "More people → more ordinary deaths."),
|
||||||
|
link(population, heatDeaths, "+", "More people → more of them exposed to the heat."),
|
||||||
|
link(
|
||||||
|
warming,
|
||||||
|
heatDeaths,
|
||||||
|
"+",
|
||||||
|
"More warming → more heat deaths. With Population this is the climate toll that ends the boom.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
// The economy overshoots the reserve and collapses (output peaks ≈540 @ t≈47,
|
||||||
|
// Capital ≈1040 @ t≈60 → ~1); Resources deplete 1000 → ~120. The carbon burned
|
||||||
|
// climbs to ~1085 ppm and holds near 1000 (warming locked at ~4 °C). Population
|
||||||
|
// overshoots last (~310 @ t≈60) then collapses to ~30 as the locked-in heat bites.
|
||||||
|
{ start: 0, stop: 250, dt: 1 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/** The gallery, ordered simplest first. */
|
/** The gallery, ordered simplest first. */
|
||||||
export const SAMPLES: Sample[] = [
|
export const SAMPLES: Sample[] = [
|
||||||
{ title: "Bathtub", blurb: "A stock filled and drained — no feedback yet.", build: bathtub },
|
{ title: "Bathtub", blurb: "A stock filled and drained — no feedback yet.", build: bathtub },
|
||||||
@@ -1495,7 +1760,14 @@ export const SAMPLES: Sample[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Bathtub with an overflow",
|
title: "Bathtub with an overflow",
|
||||||
blurb: "A flat-out tap and a spillway: the excess spills to a second Stock and the level holds.",
|
blurb:
|
||||||
|
"A flat-out tap and a spillway: the excess spills to a second Stock and the level holds.",
|
||||||
build: bathtubOverflow,
|
build: bathtubOverflow,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "World on a warming planet",
|
||||||
|
blurb:
|
||||||
|
"The Club of Rome's World3 in miniature, with a climate channel: growth overshoots a finite planet and the carbon it burns locks in the heat that finishes it.",
|
||||||
|
build: worldOnAWarmingPlanet,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user