@@ -30,16 +30,25 @@
* no brake in the structure: an arms race.
* no brake in the structure: an arms race.
* 10. Fixes that fail — a fix drains the symptom Stock (B) while its side
* 10. Fixes that fail — a fix drains the symptom Stock (B) while its side
* effect refills it (R): the cure feeds the disease.
* effect refills it (R): the cure feeds the disease.
* 11. Drift to low performance — a goal that erodes toward actual performance, so a
* 11. Drift to low performance — a goal that erodes toward actual performance, so the
* effort it drives never overcomes a steady decay: a
* Reinforcing loop ratchets both downward.
* Reinforcing loop ratchets both downward.
*
*
* Las t, the dynamic the book is named for, and the one the gallery has saved until a
* Nex t, 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 in flow ): 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
* the intervenor, ch. 5) wearing today's clothes:
*
* 13. AI deskilling spiral — handing the burden of code quality to AI atrophies the
* Expertise that holds quality up, so the team leans on AI
* harder and Technical debt spirals: addiction, not a fix.
*
*
* 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()`
@@ -528,22 +537,27 @@ function fixesThatFail(): Model {
}
}
/**
/**
* Drift to low performance — the eroding-goals trap. The Standard you hold yourself
* Drift to low performance — the eroding-goals trap. Performance is under steady
* to is not fixed: it slips toward whatever you are actually delivering. Improvement
* erosion (a constant `decay` leak: entropy, wear, rising demands), and the only
* is driven by the gap ( Standard → [+] and Performance → [− ] → improvement), and so
* thing fighting it is improvement, driven by the gap to the Standard you hold
* is slippage of the Standard (Standard → [+] and Performance → [− ] → slippage). T he
* yourself to (Standard → [+] and Performance → [− ] → improvement). Were t he
* two local Balancing loops look healthy, but together they close a Reinforcing
* Standard fixed, improvement would find a floor — Performance would settle a little
* spiral — Standard → improvement → Performance → slippage → Standard, two `− ` → R:
* below the goal (here, at 60) and hold. But the Standard is not fixed: it slips
* let Performance dip and the Standard follows it down, easing the gap, easing the
* toward whatever you are actually delivering (Standard → [+] and Performance → [− ]
* effort, so Performance drifts lower still. That R badge is the trap.
* → slippage), so there is no floor. Every notch Performance loses, the Standard
* follows down, weakening improvement, and decay carries Performance lower still —
* the loop Standard → improvement → Performance → slippage → Standard, two `− ` → R.
* That R badge is the trap: both ratchet downhill in lockstep (Performance 70 → 10,
* Standard 80 → 20), the effort forever just losing to decay.
*/
*/
function driftToLowPerformance ( ) : Model {
function driftToLowPerformance ( ) : Model {
// Performance sits low-left (fed by improvement from a Source); Standard sits
// Performance sits low-left (fed by improvement from a Source, leaked away by
// high-right (drained by slippage to a Sink). The two long links that close the
// decay to a Sink straight below it); Standard sits high-right (drained by
// Reinforcing spiral cross in the open centre, where the R badge lands.
// slippage to a Sink). The two long links that close the Reinforcing loop cross
// in the open centre, where the R badge lands.
const source = makeCloud ( { x : - 560 , y : 120 } )
const source = makeCloud ( { x : - 560 , y : 120 } )
const performance = makeStock ( { x : - 160 , y : 120 } , "Performance" )
const performance = makeStock ( { x : - 160 , y : 120 } , "Performance" )
performance . initialValue = 4 0
performance . initialValue = 7 0
const improvement = makeFlow (
const improvement = makeFlow (
midpoint ( source . position , performance . position ) ,
midpoint ( source . position , performance . position ) ,
"improvement" ,
"improvement" ,
@@ -551,24 +565,35 @@ function driftToLowPerformance(): Model {
performance . id ,
performance . id ,
)
)
// improvement closes the gap upward: 10% of (Standard − Performance), pulling
// improvement closes the gap upward: 10% of (Standard − Performance), pulling
// Performance toward the Standard.
// Performance toward the Standard — the one force resisting decay .
improvement . rule = { kind : "gap" , factor : 0.1 }
improvement . rule = { kind : "gap" , factor : 0.1 }
// decay leaks Performance away at a steady 2/step: the ever-present downward
// pressure the gap-driven improvement has to offset. Without it, two gap-closing
// flows would just meet in the middle; this is what makes the goal's erosion bite.
const decaySink = makeCloud ( { x : - 160 , y : 400 } )
const decay = makeFlow (
midpoint ( performance . position , decaySink . position ) ,
"decay" ,
performance . id ,
decaySink . id ,
)
decay . rule = { kind : "constant" , value : 2 }
const standard = makeStock ( { x : 160 , y : - 120 } , "Standard" )
const standard = makeStock ( { x : 160 , y : - 120 } , "Standard" )
standard . initialValue = 80
standard . initialValue = 80
const sink = makeCloud ( { x : 560 , y : - 120 } )
const slippageS ink = makeCloud ( { x : 560 , y : - 120 } )
const slippage = makeFlow (
const slippage = makeFlow (
midpoint ( standard . position , sink . position ) ,
midpoint ( standard . position , slippageS ink . position ) ,
"slippage" ,
"slippage" ,
standard . id ,
standard . id ,
sink . id ,
slippageS ink . id ,
)
)
// slippage erodes the *same* gap from the other side: the Standard drifts down
// slippage erodes the Standard toward actual Performance, also at 10% of the gap.
// toward actual Performance. Both gaps close, so they meet — the Standard has
// It is what removes the floor: the Standard sags after Performance instead of
// sagged from 80 to the middle, the eroding-goal trap .
// holding above it, so the gap stays open at a fixed 10 while both slide down .
slippage . rule = { kind : "gap" , factor : 0.1 }
slippage . rule = { kind : "gap" , factor : 0.1 }
return model (
return model (
"Drift to low performance" ,
"Drift to low performance" ,
[ source , performance , improvement , standard , sink , slippage ] ,
[ source , performance , improvement , decay , decaySink , standard , slippageS ink , slippage ] ,
[
[
link ( standard , improvement , "+" ) ,
link ( standard , improvement , "+" ) ,
link ( performance , improvement , "-" ) ,
link ( performance , improvement , "-" ) ,
@@ -580,67 +605,206 @@ 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 tha t
* Resource with a point of no return. A fishery (Fish) regrows on its own, bu t
* only depletes: a Stock with no inflow, the first in the gallery. An econom y
* reproduction needs fish to find each other: spawning scales with densit y
* (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 (ext rac tion → [+] → investment → Capital), so the loop
* density the quad ratic births win and the stock climbs to its carrying capacity
* Capital → extraction → investment → C apital carries no `− ` → Reinforcing. Capital
* (crowding deaths ~Fish³ c ap it there); *below* it the linear deaths win and the
* climbs and extra ction accelerates, but every unit burned is gone for good, so the
* stock slides to an extin ction it cannot climb back from — the Allee threshold,
* Resource crosses the b reak-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 settle s
* A fishing fleet (Boats) reinvests its catch into more boat s
* 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
// c oupling links — Capital → extraction and extraction → investment — cross in the
// S ource → fleet growth → Boats → scrapping → S ink 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 : - 2 40, y : 0 } , "Resource " )
const fish = makeStock ( { x : - 42 0 , 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.01 }
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 drift up toward carrying capacity (~1170) while the fleet compounds, then the
// starting level by t=150 — the full boom-and-bust arc, no dead tail.
// catch overshoots the renewal rate and pulls them past the Allee threshold (~30) at
// t≈43, after which they go extinct and stay there; Boats overshoot to ~700 (t≈37)
// and collapse back near their start by t=150.
{ start : 0 , stop : 150 , dt : 1 } ,
{ start : 0 , stop : 150 , dt : 1 } ,
)
)
}
}
/**
* AI deskilling spiral — a classic trap in today's clothes: "Shifting the burden to
* the intervenor" (Thinking in Systems, ch. 5), where leaning on an outside fixer
* atrophies your own capacity to solve the problem, so you depend on the fixer ever
* more. Here the intervenor is AI. Technical debt drives reliance on it (the more cruft
* and delivery pressure, the more you reach for the model: Technical debt → [+] → AI
* reliance); AI churns out plausible code that adds debt (AI reliance → [+] → debt
* accrual) and lets skills lapse (AI reliance → [+] → atrophy, draining Expertise); and
* a thinner-skilled team refactors less (Expertise → [+] → refactoring, the Balancing
* payoff that now weakens). The loop Technical debt → AI reliance → atrophy → Expertise
* → refactoring → Technical debt carries two `− ` (the two outflows) → Reinforcing: the
* spiral. The hopeful brake is learning — practice pulling Expertise back toward its
* ceiling (skill ceiling → [+], Expertise → [− ] → learning: a Balancing loop) — but
* tuned here it loses to the spiral. (Code quality and lead time aren't nodes: quality
* reads as the inverse of Technical debt, lead time as the inverse of Expertise;
* "model price" lives in the AI-reliance factor — cheaper models, higher reliance.)
*/
function aiDeskillingSpiral ( ) : Model {
// Two lanes — Expertise on top, Technical debt below — each a full Source → inflow →
// Stock → outflow → Sink. The AI reliance Converter sits between them, reading the
// debt below and feeding both atrophy (top lane) and debt accrual: the couplings that
// close the Reinforcing spiral cross the open centre.
const skillCeiling = makeConverter ( { x : - 360 , y : - 300 } , "skill ceiling" )
skillCeiling . rule = { kind : "constant" , value : 100 }
const learningSource = makeCloud ( { x : - 540 , y : - 120 } )
const expertise = makeStock ( { x : - 180 , y : - 120 } , "Expertise" )
expertise . initialValue = 70
const learning = makeFlow ( { x : - 360 , y : - 120 } , "learning" , learningSource . id , expertise . id )
// learning = factor × (skill ceiling − Expertise): practice pulls skill back up — the
// Balancing brake. The further from mastery, the harder you study.
learning . rule = { kind : "gap" , factor : 0.04 }
const atrophySink = makeCloud ( { x : 300 , y : - 120 } )
const atrophy = makeFlow ( { x : 80 , y : - 120 } , "atrophy" , expertise . id , atrophySink . id )
// atrophy = factor × AI reliance: the more you offload to AI, the faster unused skills
// lapse — the side effect that makes this an addiction, not a fix.
atrophy . rule = { kind : "proportional" , factor : 0.6 }
const accrualSource = makeCloud ( { x : - 540 , y : 120 } )
const debt = makeStock ( { x : - 180 , y : 120 } , "Technical debt" )
debt . initialValue = 20
const accrual = makeFlow ( { x : - 360 , y : 120 } , "debt accrual" , accrualSource . id , debt . id )
// debt accrual = factor × AI reliance: AI emits plausible code faster than anyone
// reviews it, so debt grows the more you lean on it.
accrual . rule = { kind : "proportional" , factor : 0.9 }
const refactorSink = makeCloud ( { x : 300 , y : 120 } )
const refactoring = makeFlow ( { x : 80 , y : 120 } , "refactoring" , debt . id , refactorSink . id )
// refactoring = factor × Expertise × Technical debt: skilled teams pay debt down in
// proportion to how much there is — the Balancing payoff the spiral starves.
refactoring . rule = { kind : "proportional" , factor : 0.0009 }
const reliance = makeConverter ( { x : - 180 , y : 0 } , "AI reliance" )
// AI reliance = factor × Technical debt: the factor is how cheap and available models
// are — lower model price, higher reliance per unit of debt.
reliance . rule = { kind : "proportional" , factor : 0.1 }
return model (
"AI deskilling spiral" ,
[
skillCeiling ,
learningSource ,
expertise ,
learning ,
atrophySink ,
atrophy ,
accrualSource ,
debt ,
accrual ,
refactorSink ,
refactoring ,
reliance ,
] ,
[
link ( skillCeiling , learning , "+" ) ,
link ( expertise , learning , "-" ) ,
link ( reliance , atrophy , "+" ) ,
link ( debt , reliance , "+" ) ,
link ( reliance , accrual , "+" ) ,
link ( expertise , refactoring , "+" ) ,
link ( debt , refactoring , "+" ) ,
] ,
// Expertise slides 70 → ~6 and Technical debt spirals 20 → ~150 over the window —
// the Reinforcing loop clearly taking off, stopped (like "Escalation") before it
// runs away off-chart.
{ start : 0 , stop : 50 , 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 } ,
@@ -691,12 +855,17 @@ export const SAMPLES: Sample[] = [
} ,
} ,
{
{
title : "Drift to low performance" ,
title : "Drift to low performance" ,
blurb : "Goals erode toward actual: a Reinforcing slide downhill." ,
blurb : "An eroding goal leaves steady decay no floor: both slide downhill." ,
build : driftToLowPerformance ,
build : driftToLowPerformance ,
} ,
} ,
{
{
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 ,
} ,
} ,
{
title : "AI deskilling spiral" ,
blurb : "Leaning on AI to hold quality erodes the expertise that holds it: shifting the burden." ,
build : aiDeskillingSpiral ,
} ,
]
]