feat(model): add fixes-that-fail and drift traps to the gallery
Two more Meadows system traps: Fixes that fail (a balancing road-building fix whose induced-driving side effect reinforces the congestion it targets) and Drift to low performance (an eroding goal that spirals actual performance and the standard downward). Also re-lays-out the coupled-stock samples for readability: predator and prey and escalation now use aligned two-row layouts, and fixes that fail stacks its flow valves in one column so the backfire link reads as a clean vertical.
This commit is contained in:
@@ -21,13 +21,17 @@
|
|||||||
* 6. Predator and prey — two coupled Stocks whose interlocking loops oscillate.
|
* 6. Predator and prey — two coupled Stocks whose interlocking loops oscillate.
|
||||||
* 7. Epidemic — a chain of Stocks joined by Stock→Stock Flows: no clouds.
|
* 7. Epidemic — a chain of Stocks joined by Stock→Stock Flows: no clouds.
|
||||||
*
|
*
|
||||||
* Last, two of Donella Meadows' system *traps* — structures that reliably misbehave
|
* Last, four of Donella Meadows' system *traps* — structures that reliably misbehave
|
||||||
* (Thinking in Systems, ch. 5), to contrast the healthy dynamics above:
|
* (Thinking in Systems, ch. 5), to contrast the healthy dynamics above:
|
||||||
*
|
*
|
||||||
* 8. Tragedy of the commons — competing Reinforcing loops drain a shared Stock
|
* 8. Tragedy of the commons — competing Reinforcing loops drain a shared Stock
|
||||||
* faster than its weak, shared Balancing brake reacts.
|
* faster than its weak, shared Balancing brake reacts.
|
||||||
* 9. Escalation — a single Reinforcing loop spanning two Stocks, with
|
* 9. Escalation — a single Reinforcing loop spanning two Stocks, with
|
||||||
* 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
|
||||||
|
* effect refills it (R): the cure feeds the disease.
|
||||||
|
* 11. Drift to low performance — a goal that erodes toward actual performance, so a
|
||||||
|
* Reinforcing loop ratchets both downward.
|
||||||
*
|
*
|
||||||
* 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()`
|
||||||
@@ -188,11 +192,13 @@ function limitsToGrowth(): Model {
|
|||||||
* around it is what makes the two populations oscillate.
|
* around it is what makes the two populations oscillate.
|
||||||
*/
|
*/
|
||||||
function predatorPrey(): Model {
|
function predatorPrey(): Model {
|
||||||
// Rabbits run along the top row, foxes along a lower row, so the two long
|
// Two aligned rows flowing left→right — Rabbits on top, Foxes below — each a full
|
||||||
// coupling links cross in the open space between the rows.
|
// Source → birth → Stock → outflow → Sink lane. The two coupling links (Rabbits →
|
||||||
const preySource = makeCloud({ x: -520, y: 0 })
|
// fox births, Foxes → predation) run as clear diagonals between the rows, so the
|
||||||
const rabbits = makeStock({ x: -260, y: 0 }, "Rabbits")
|
// cross-stock loop traces a circuit through the open centre.
|
||||||
const preySink = makeCloud({ x: 0, y: 0 })
|
const preySource = makeCloud({ x: -480, y: -140 })
|
||||||
|
const rabbits = makeStock({ x: -80, y: -140 }, "Rabbits")
|
||||||
|
const preySink = makeCloud({ x: 320, y: -140 })
|
||||||
const rabbitBirths = makeFlow(
|
const rabbitBirths = makeFlow(
|
||||||
midpoint(preySource.position, rabbits.position),
|
midpoint(preySource.position, rabbits.position),
|
||||||
"rabbit births",
|
"rabbit births",
|
||||||
@@ -205,9 +211,9 @@ function predatorPrey(): Model {
|
|||||||
rabbits.id,
|
rabbits.id,
|
||||||
preySink.id,
|
preySink.id,
|
||||||
)
|
)
|
||||||
const foxSource = makeCloud({ x: 120, y: 220 })
|
const foxSource = makeCloud({ x: -480, y: 140 })
|
||||||
const foxes = makeStock({ x: 380, y: 220 }, "Foxes")
|
const foxes = makeStock({ x: -80, y: 140 }, "Foxes")
|
||||||
const foxSink = makeCloud({ x: 640, y: 220 })
|
const foxSink = makeCloud({ x: 320, y: 140 })
|
||||||
const foxBirths = makeFlow(
|
const foxBirths = makeFlow(
|
||||||
midpoint(foxSource.position, foxes.position),
|
midpoint(foxSource.position, foxes.position),
|
||||||
"fox births",
|
"fox births",
|
||||||
@@ -352,31 +358,102 @@ function tragedyOfTheCommons(): Model {
|
|||||||
* into oscillation instead of exploding.)
|
* into oscillation instead of exploding.)
|
||||||
*/
|
*/
|
||||||
function escalation(): Model {
|
function escalation(): Model {
|
||||||
// Both arsenals sit inboard with their Sources outside; the two links crossing
|
// Two parallel rows — Blue on top, Red below — each flowing left→right from a
|
||||||
// the centre are the escalation loop.
|
// Source to its arsenal. The two cross-coupling links span the open centre and
|
||||||
const redSource = makeCloud({ x: -520, y: 0 })
|
// cross there, where the R badge lands, so the whole loop reads at a glance.
|
||||||
const redArsenal = makeStock({ x: -260, y: 0 }, "Red arsenal")
|
const blueSource = makeCloud({ x: -560, y: -120 })
|
||||||
const redBuildup = makeFlow(
|
const blueArsenal = makeStock({ x: 300, y: -120 }, "Blue arsenal")
|
||||||
midpoint(redSource.position, redArsenal.position),
|
|
||||||
"Red buildup",
|
|
||||||
redSource.id,
|
|
||||||
redArsenal.id,
|
|
||||||
)
|
|
||||||
const blueSource = makeCloud({ x: 520, y: 0 })
|
|
||||||
const blueArsenal = makeStock({ x: 260, y: 0 }, "Blue arsenal")
|
|
||||||
const blueBuildup = makeFlow(
|
const blueBuildup = makeFlow(
|
||||||
midpoint(blueSource.position, blueArsenal.position),
|
midpoint(blueSource.position, blueArsenal.position),
|
||||||
"Blue buildup",
|
"Blue buildup",
|
||||||
blueSource.id,
|
blueSource.id,
|
||||||
blueArsenal.id,
|
blueArsenal.id,
|
||||||
)
|
)
|
||||||
|
const redSource = makeCloud({ x: -560, y: 120 })
|
||||||
|
const redArsenal = makeStock({ x: 300, y: 120 }, "Red arsenal")
|
||||||
|
const redBuildup = makeFlow(
|
||||||
|
midpoint(redSource.position, redArsenal.position),
|
||||||
|
"Red buildup",
|
||||||
|
redSource.id,
|
||||||
|
redArsenal.id,
|
||||||
|
)
|
||||||
return model(
|
return model(
|
||||||
"Escalation",
|
"Escalation",
|
||||||
[redSource, redArsenal, redBuildup, blueSource, blueArsenal, blueBuildup],
|
[blueSource, blueArsenal, blueBuildup, redSource, redArsenal, redBuildup],
|
||||||
[link(blueArsenal, redBuildup, "+"), link(redArsenal, blueBuildup, "+")],
|
[link(blueArsenal, redBuildup, "+"), link(redArsenal, blueBuildup, "+")],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixes that fail — the archetype where a quick fix relieves a symptom but feeds it
|
||||||
|
* through a side effect, so the symptom returns and the fix is reapplied for ever.
|
||||||
|
* High Congestion prompts road building, and the new capacity drains it (Congestion
|
||||||
|
* → [+] → road building, an outflow: a Balancing fix). But roads also induce driving
|
||||||
|
* (road building → [+] → driving), and that extra traffic refills Congestion — the
|
||||||
|
* loop Congestion → road building → driving → Congestion carries no `−`, so it is
|
||||||
|
* Reinforcing: the cure feeds the disease, and you cannot build your way out of
|
||||||
|
* traffic.
|
||||||
|
*/
|
||||||
|
function fixesThatFail(): Model {
|
||||||
|
// Both flow valves share the left column (x = -120) so the backfire link
|
||||||
|
// road building → driving drops as a clean vertical, not a curl. Congestion sits
|
||||||
|
// on the right at driving's height; its road-building outflow runs up-left to the
|
||||||
|
// valve and on to the Sink, so the Reinforcing loop reads as the left edge plus
|
||||||
|
// the diagonal back to Congestion. Placed by hand, not midpoint, to hold the column.
|
||||||
|
const source = makeCloud({ x: -420, y: 120 })
|
||||||
|
const congestion = makeStock({ x: 300, y: 120 }, "Congestion")
|
||||||
|
const driving = makeFlow({ x: -120, y: 120 }, "driving", source.id, congestion.id)
|
||||||
|
const sink = makeCloud({ x: 300, y: -160 })
|
||||||
|
const roadBuilding = makeFlow({ x: -120, y: -160 }, "road building", congestion.id, sink.id)
|
||||||
|
return model(
|
||||||
|
"Fixes that fail",
|
||||||
|
[source, congestion, driving, sink, roadBuilding],
|
||||||
|
[link(congestion, roadBuilding, "+"), link(roadBuilding, driving, "+")],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drift to low performance — the eroding-goals trap. The Standard you hold yourself
|
||||||
|
* to is not fixed: it slips toward whatever you are actually delivering. Improvement
|
||||||
|
* is driven by the gap (Standard → [+] and Performance → [−] → improvement), and so
|
||||||
|
* is slippage of the Standard (Standard → [+] and Performance → [−] → slippage). The
|
||||||
|
* two local Balancing loops look healthy, but together they close a Reinforcing
|
||||||
|
* spiral — Standard → improvement → Performance → slippage → Standard, two `−` → R:
|
||||||
|
* let Performance dip and the Standard follows it down, easing the gap, easing the
|
||||||
|
* effort, so Performance drifts lower still. That R badge is the trap.
|
||||||
|
*/
|
||||||
|
function driftToLowPerformance(): Model {
|
||||||
|
// Performance sits low-left (fed by improvement from a Source); Standard sits
|
||||||
|
// high-right (drained by slippage to a Sink). The two long links that close the
|
||||||
|
// Reinforcing spiral cross in the open centre, where the R badge lands.
|
||||||
|
const source = makeCloud({ x: -560, y: 120 })
|
||||||
|
const performance = makeStock({ x: -160, y: 120 }, "Performance")
|
||||||
|
const improvement = makeFlow(
|
||||||
|
midpoint(source.position, performance.position),
|
||||||
|
"improvement",
|
||||||
|
source.id,
|
||||||
|
performance.id,
|
||||||
|
)
|
||||||
|
const standard = makeStock({ x: 160, y: -120 }, "Standard")
|
||||||
|
const sink = makeCloud({ x: 560, y: -120 })
|
||||||
|
const slippage = makeFlow(
|
||||||
|
midpoint(standard.position, sink.position),
|
||||||
|
"slippage",
|
||||||
|
standard.id,
|
||||||
|
sink.id,
|
||||||
|
)
|
||||||
|
return model(
|
||||||
|
"Drift to low performance",
|
||||||
|
[source, performance, improvement, standard, sink, slippage],
|
||||||
|
[
|
||||||
|
link(standard, improvement, "+"),
|
||||||
|
link(performance, improvement, "-"),
|
||||||
|
link(standard, slippage, "+"),
|
||||||
|
link(performance, slippage, "-"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/** 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 },
|
||||||
@@ -420,4 +497,14 @@ export const SAMPLES: Sample[] = [
|
|||||||
blurb: "An arms race: one Reinforcing loop spanning two Stocks.",
|
blurb: "An arms race: one Reinforcing loop spanning two Stocks.",
|
||||||
build: escalation,
|
build: escalation,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Fixes that fail",
|
||||||
|
blurb: "Road building eases congestion (B) but induces the traffic that refills it (R).",
|
||||||
|
build: fixesThatFail,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Drift to low performance",
|
||||||
|
blurb: "Goals erode toward actual: a Reinforcing slide downhill.",
|
||||||
|
build: driftToLowPerformance,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user