feat(samples): rework "Overshoot and collapse" as a renewable fishery
The non-renewable Resource (a Stock with no inflow) becomes a renewable fishery with a true point of no return — an Allee threshold. Spawning scales with density (~Fish^2) while natural deaths are linear (~Fish), so below a critical density the deaths win and the stock slides to an extinction it never recovers from; crowding deaths (~Fish^3) cap a healthy stock at carrying capacity. A reinvesting fleet (Boats, Reinforcing) overshoots the renewal rate and drags the fish under the threshold, then starves and scraps itself. The negative-positive-negative regrowth curve is the one shape the proportional rule can't draw alone, so two relay Converters build it (density to lift spawning to ~Fish^2, crowding for the ~Fish^3 ceiling) — the Limits-to-growth crowding trick, doubled. At 16 nodes this is the gallery's largest model and the only one with a Converter feeding a Converter. Tuned against the engine: Fish hold near 1000, cross the threshold (~200) at t~40 as the catch overshoots, then go extinct and stay there; Boats overshoot to ~450 and collapse back near their start by t=150. No divergence; loops classify as expected (R: fleet reinvestment, birth engine; B: natural/crowding/catch drains, scrapping).
This commit is contained in:
@@ -36,10 +36,11 @@
|
|||||||
* Next, the dynamic the book is named for, and the one the gallery has saved until a
|
* Next, the dynamic the book is named for, and the one the gallery has saved until a
|
||||||
* reader knows every piece it needs:
|
* reader knows every piece it needs:
|
||||||
*
|
*
|
||||||
* 12. Overshoot and collapse — a Reinforcing engine running on a *non-renewable*
|
* 12. Overshoot and collapse — a Reinforcing harvester on a *renewable* Resource with
|
||||||
* Stock (the first with no inflow): it overshoots the
|
* an extinction threshold (an Allee floor): a fleet
|
||||||
* limit instead of settling at it, the dark twin of
|
* overshoots the renewal rate and pushes the fishery past
|
||||||
* "Limits to growth" — the ceiling erodes, so it crashes.
|
* the point of no return. The dark twin of "Limits to
|
||||||
|
* growth" — the limit doesn't hold, it collapses for good.
|
||||||
*
|
*
|
||||||
* Last, the language pointed at a live debate — a classic trap (Shifting the burden to
|
* Last, the language pointed at a live debate — a classic trap (Shifting the burden to
|
||||||
* the intervenor, ch. 5) wearing today's clothes:
|
* the intervenor, ch. 5) wearing today's clothes:
|
||||||
@@ -587,63 +588,117 @@ function driftToLowPerformance(): Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overshoot and collapse — the dark twin of "Limits to growth". The same
|
* Overshoot and collapse — the dark twin of "Limits to growth", on a *renewable*
|
||||||
* Reinforcing engine runs, but the limit here is a *non-renewable* Resource that
|
* Resource with a point of no return. A fishery (Fish) regrows on its own, but
|
||||||
* only depletes: a Stock with no inflow, the first in the gallery. An economy
|
* reproduction needs fish to find each other: spawning scales with density
|
||||||
* (Capital) lives off it — extraction grows with both the Resource left and the
|
* (spawning = factor × Fish × density, density ∝ Fish, so ~Fish²), while natural
|
||||||
* Capital deployed (Resource, Capital → [+] → extraction), and the revenue is
|
* deaths are merely linear (natural deaths = factor × Fish). Above a critical
|
||||||
* reinvested as new Capital (extraction → [+] → investment → Capital), so the loop
|
* density the quadratic births win and the stock climbs to its carrying capacity
|
||||||
* Capital → extraction → investment → Capital carries no `−` → Reinforcing. Capital
|
* (crowding deaths ~Fish³ cap it there); *below* it the linear deaths win and the
|
||||||
* climbs and extraction accelerates, but every unit burned is gone for good, so the
|
* stock slides to an extinction it cannot climb back from — the Allee threshold,
|
||||||
* Resource crosses the break-even level, the engine starves, and depreciation
|
* the renewable resource's hidden floor.
|
||||||
* (Capital → [+] → depreciation, a Balancing drain) takes Capital down: it peaks,
|
*
|
||||||
* then collapses. Contrast "Predator and prey", whose prey regrows and so settles
|
* A fishing fleet (Boats) reinvests its catch into more boats
|
||||||
* into oscillation — a finite Resource cannot, so it overshoots and crashes instead.
|
* (Boats → catch → fleet growth → Boats, no `−` → Reinforcing), so the catch
|
||||||
|
* (catch = factor × Fish × Boats) accelerates and overshoots the renewal rate,
|
||||||
|
* dragging Fish under the threshold. Once there it is too late: even as the catch
|
||||||
|
* starves and the fleet scraps itself (Boats → [+] → scrapping, a Balancing drain),
|
||||||
|
* the Fish are gone for good and never recover. Contrast "Predator and prey", whose
|
||||||
|
* prey regrows from any level and so oscillates forever — here the prey has a floor
|
||||||
|
* it cannot climb back from, so a Reinforcing harvester collapses it permanently.
|
||||||
|
*
|
||||||
|
* The Allee curve is the one shape the proportional rule cannot draw alone (it needs
|
||||||
|
* net regrowth to go negative–positive–negative), so two relays build it: `density`
|
||||||
|
* (∝ Fish) lifts spawning to ~Fish², and `crowding` (∝ Fish²) lifts crowding deaths
|
||||||
|
* to ~Fish³ — the same crowding trick as "Limits to growth", doubled. The gallery's
|
||||||
|
* largest model, and the only one that needs a Converter feeding a Converter.
|
||||||
*/
|
*/
|
||||||
function overshootAndCollapse(): Model {
|
function overshootAndCollapse(): Model {
|
||||||
// Resource on the left drains only downward (no inflow). Capital on the right runs
|
// Fish (left) carries the whole renewal engine: a spawning inflow from the top, and
|
||||||
// a full Source → investment → Capital → depreciation → Sink column. The two
|
// three drains — natural deaths, crowding deaths, and the catch. Boats (right) runs a
|
||||||
// coupling links — Capital → extraction and extraction → investment — cross in the
|
// Source → fleet growth → Boats → scrapping → Sink column. The two coupling links —
|
||||||
// open centre, where the R badge lands.
|
// Boats → catch and catch → fleet growth — cross the open centre, where the R badge lands.
|
||||||
const resource = makeStock({ x: -240, y: 0 }, "Resource")
|
const fish = makeStock({ x: -420, y: 0 }, "Fish")
|
||||||
resource.initialValue = 1000
|
fish.initialValue = 1000
|
||||||
const extractionSink = makeCloud({ x: -240, y: 360 })
|
fish.unit = "tonnes"
|
||||||
const extraction = makeFlow({ x: -240, y: 160 }, "extraction", resource.id, extractionSink.id)
|
// density ∝ Fish: how easily fish meet to spawn. Relays Fish into the births term so
|
||||||
// extraction = factor × Resource × Capital (both `+`): more capital extracts
|
// spawning reads as ~Fish² — the Allee mechanism (sparse fish breed slowly).
|
||||||
// faster, scarcer resource slower. The bilinear term the non-negative floor tames.
|
const density = makeConverter({ x: -700, y: -80 }, "density")
|
||||||
extraction.rule = { kind: "proportional", factor: 0.0004 }
|
density.rule = { kind: "proportional", factor: 1 }
|
||||||
const capital = makeStock({ x: 240, y: 0 }, "Capital")
|
// crowding ∝ Fish² (Fish × density): the overcrowding pressure that lifts crowding
|
||||||
capital.initialValue = 5
|
// deaths to ~Fish³, so the stock plateaus at its carrying capacity. A Converter read
|
||||||
const investmentSource = makeCloud({ x: 240, y: -360 })
|
// by a Converter — the only such wiring in the gallery.
|
||||||
const investment = makeFlow({ x: 240, y: -160 }, "investment", investmentSource.id, capital.id)
|
const crowding = makeConverter({ x: -700, y: 80 }, "crowding")
|
||||||
// investment = factor × extraction (its one `+` input): the revenue reinvested —
|
crowding.rule = { kind: "proportional", factor: 1 }
|
||||||
// a Flow feeding a Flow, the edge that closes the Reinforcing loop through Capital.
|
const spawnSource = makeCloud({ x: -420, y: -320 })
|
||||||
investment.rule = { kind: "proportional", factor: 0.5 }
|
const spawning = makeFlow({ x: -420, y: -160 }, "spawning", spawnSource.id, fish.id)
|
||||||
const depreciationSink = makeCloud({ x: 240, y: 360 })
|
// spawning = factor × Fish × density (~Fish²): the Reinforcing birth engine that
|
||||||
const depreciation = makeFlow({ x: 240, y: 160 }, "depreciation", capital.id, depreciationSink.id)
|
// needs a crowd — it falls away faster than deaths as the Fish thin out.
|
||||||
// depreciation = factor × Capital (its `+` input): the Balancing drain that wins
|
spawning.rule = { kind: "proportional", factor: 0.00036 }
|
||||||
// once the Resource can no longer feed investment.
|
const deathSink = makeCloud({ x: -720, y: 280 })
|
||||||
depreciation.rule = { kind: "proportional", factor: 0.04 }
|
const naturalDeaths = makeFlow({ x: -580, y: 180 }, "natural deaths", fish.id, deathSink.id)
|
||||||
|
// natural deaths = factor × Fish (linear): the Balancing drain that *wins* below the
|
||||||
|
// Allee threshold, where ~Fish² spawning can no longer keep up — and extinction follows.
|
||||||
|
naturalDeaths.rule = { kind: "proportional", factor: 0.06 }
|
||||||
|
const crowdSink = makeCloud({ x: -420, y: 320 })
|
||||||
|
const crowdingDeaths = makeFlow({ x: -420, y: 160 }, "crowding deaths", fish.id, crowdSink.id)
|
||||||
|
// crowding deaths = factor × Fish × crowding (~Fish³): the steep Balancing ceiling
|
||||||
|
// that holds the healthy stock at carrying capacity.
|
||||||
|
crowdingDeaths.rule = { kind: "proportional", factor: 3e-7 }
|
||||||
|
const catchSink = makeCloud({ x: -90, y: 220 })
|
||||||
|
const catching = makeFlow({ x: -255, y: 90 }, "catch", fish.id, catchSink.id)
|
||||||
|
// catch = factor × Fish × Boats (both `+`): more boats and more fish both lift the
|
||||||
|
// haul. This is what overshoots the renewal rate and pulls Fish under the threshold.
|
||||||
|
catching.rule = { kind: "proportional", factor: 0.0004 }
|
||||||
|
const boats = makeStock({ x: 420, y: 0 }, "Boats")
|
||||||
|
boats.initialValue = 5
|
||||||
|
const fleetSource = makeCloud({ x: 420, y: -320 })
|
||||||
|
const fleetGrowth = makeFlow({ x: 420, y: -160 }, "fleet growth", fleetSource.id, boats.id)
|
||||||
|
// fleet growth = factor × catch (its one `+` input): the revenue reinvested — a Flow
|
||||||
|
// feeding a Flow, the edge that closes the Reinforcing loop through Boats.
|
||||||
|
fleetGrowth.rule = { kind: "proportional", factor: 0.5 }
|
||||||
|
const scrapSink = makeCloud({ x: 420, y: 320 })
|
||||||
|
const scrapping = makeFlow({ x: 420, y: 160 }, "scrapping", boats.id, scrapSink.id)
|
||||||
|
// scrapping = factor × Boats (its `+` input): the Balancing drain that takes the fleet
|
||||||
|
// down once the catch can no longer feed fleet growth.
|
||||||
|
scrapping.rule = { kind: "proportional", factor: 0.04 }
|
||||||
return model(
|
return model(
|
||||||
"Overshoot and collapse",
|
"Overshoot and collapse",
|
||||||
[
|
[
|
||||||
resource,
|
fish,
|
||||||
extractionSink,
|
density,
|
||||||
extraction,
|
crowding,
|
||||||
capital,
|
spawnSource,
|
||||||
investmentSource,
|
spawning,
|
||||||
investment,
|
deathSink,
|
||||||
depreciationSink,
|
naturalDeaths,
|
||||||
depreciation,
|
crowdSink,
|
||||||
|
crowdingDeaths,
|
||||||
|
catchSink,
|
||||||
|
catching,
|
||||||
|
boats,
|
||||||
|
fleetSource,
|
||||||
|
fleetGrowth,
|
||||||
|
scrapSink,
|
||||||
|
scrapping,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
link(resource, extraction, "+"),
|
link(fish, density, "+"),
|
||||||
link(capital, extraction, "+"),
|
link(fish, crowding, "+"),
|
||||||
link(extraction, investment, "+"),
|
link(density, crowding, "+"),
|
||||||
link(capital, depreciation, "+"),
|
link(fish, spawning, "+"),
|
||||||
|
link(density, spawning, "+"),
|
||||||
|
link(fish, naturalDeaths, "+"),
|
||||||
|
link(fish, crowdingDeaths, "+"),
|
||||||
|
link(crowding, crowdingDeaths, "+"),
|
||||||
|
link(fish, catching, "+"),
|
||||||
|
link(boats, catching, "+"),
|
||||||
|
link(catching, fleetGrowth, "+"),
|
||||||
|
link(boats, scrapping, "+"),
|
||||||
],
|
],
|
||||||
// Capital starts at 5, overshoots to ~250 by t≈39, and collapses back near its
|
// Fish hold near carrying capacity (1000) while the fleet compounds, cross the Allee
|
||||||
// starting level by t=150 — the full boom-and-bust arc, no dead tail.
|
// threshold (~200) around t≈40 as the catch overshoots, then go extinct and stay
|
||||||
|
// there; Boats overshoot to ~450 (t≈40) and collapse back near their start by t=150.
|
||||||
{ start: 0, stop: 150, dt: 1 },
|
{ start: 0, stop: 150, dt: 1 },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -787,7 +842,7 @@ export const SAMPLES: Sample[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Overshoot and collapse",
|
title: "Overshoot and collapse",
|
||||||
blurb: "A growth engine burns a finite Resource: it peaks, then crashes.",
|
blurb: "A fleet overfishes past the point of no return: the stock collapses for good.",
|
||||||
build: overshootAndCollapse,
|
build: overshootAndCollapse,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user