style(model): align every sample layout to the 20px grid

Snap all sample coordinates to multiples of 20 so they sit on the
editor grid and read cleanly. Computed midpoint() valves had to land
on grid points too, so a few endpoints were nudged (e.g. escalation
arsenals 300->280, limits-to-growth source -300->-280). Population is
re-laid as a grid cascade so its links no longer overlap the pipes.
This commit is contained in:
Julien Calixte
2026-06-20 11:19:37 +02:00
parent d7d0966adc
commit df03012c8b

View File

@@ -68,9 +68,9 @@ function model(name: string, nodes: ModelNode[], infoLinks: InformationLink[]):
* feedback: it just shows the substrate (Stock, Flow, Source, Sink). * feedback: it just shows the substrate (Stock, Flow, Source, Sink).
*/ */
function bathtub(): Model { function bathtub(): Model {
const source = makeCloud({ x: -278, y: -18 }) const source = makeCloud({ x: -280, y: 0 })
const water = makeStock({ x: -48, y: -20 }, "Water") const water = makeStock({ x: 0, y: 0 }, "Water")
const sink = makeCloud({ x: 242, y: -18 }) const sink = makeCloud({ x: 280, y: 0 })
const filling = makeFlow( const filling = makeFlow(
midpoint(source.position, water.position), midpoint(source.position, water.position),
"filling", "filling",
@@ -90,8 +90,8 @@ function savings(): Model {
// Source up-left, Balance down-right: the interest valve lands at their // Source up-left, Balance down-right: the interest valve lands at their
// midpoint (above Balance), so the `Balance → interest` link arcs back up as a // midpoint (above Balance), so the `Balance → interest` link arcs back up as a
// visible Reinforcing loop instead of overlapping the inflow pipe. // visible Reinforcing loop instead of overlapping the inflow pipe.
const source = makeCloud({ x: -250, y: -70 }) const source = makeCloud({ x: -240, y: -80 })
const balance = makeStock({ x: 110, y: 30 }, "Balance") const balance = makeStock({ x: 120, y: 40 }, "Balance")
const interest = makeFlow( const interest = makeFlow(
midpoint(source.position, balance.position), midpoint(source.position, balance.position),
"interest", "interest",
@@ -108,15 +108,15 @@ function savings(): Model {
* (outflow) → Coffee has one `` → Balancing: it settles toward room temperature. * (outflow) → Coffee has one `` → Balancing: it settles toward room temperature.
*/ */
function coffee(): Model { function coffee(): Model {
const coffee = makeStock({ x: -200, y: -10 }, "Coffee") const coffee = makeStock({ x: -200, y: 0 }, "Coffee")
const sink = makeCloud({ x: 190, y: -8 }) const sink = makeCloud({ x: 200, y: 0 })
const heatLoss = makeFlow( const heatLoss = makeFlow(
midpoint(coffee.position, sink.position), midpoint(coffee.position, sink.position),
"heat loss", "heat loss",
coffee.id, coffee.id,
sink.id, sink.id,
) )
const room = makeConverter({ x: -29, y: -150 }, "room temperature") const room = makeConverter({ x: 0, y: -160 }, "room temperature")
return model( return model(
"Coffee cooling", "Coffee cooling",
[coffee, sink, heatLoss, room], [coffee, sink, heatLoss, room],
@@ -131,18 +131,17 @@ function coffee(): Model {
* births (`+`), life expectancy lowers deaths (``). * births (`+`), life expectancy lowers deaths (``).
*/ */
function population(): Model { function population(): Model {
const source = makeCloud({ x: -360, y: -8 }) // A grid-aligned cascade (20px grid): Source and both Converters stack in the left
const people = makeStock({ x: -48, y: -10 }, "Population") // column, the Stock sits at the centre, and births → Population → deaths step down
const sink = makeCloud({ x: 250, y: -8 }) // toward the Sink — so every Information Link lands in open space, not on a pipe.
const births = makeFlow( // Valves are placed by hand, not at the midpoint, to hold the steps.
midpoint(source.position, people.position), const source = makeCloud({ x: -360, y: -240 })
"births", const fertility = makeConverter({ x: -360, y: -40 }, "fertility")
source.id, const lifeExpectancy = makeConverter({ x: -360, y: 240 }, "life expectancy")
people.id, const people = makeStock({ x: 0, y: 0 }, "Population")
) const births = makeFlow({ x: -160, y: -160 }, "births", source.id, people.id)
const deaths = makeFlow(midpoint(people.position, sink.position), "deaths", people.id, sink.id) const sink = makeCloud({ x: 360, y: 240 })
const fertility = makeConverter({ x: -204, y: -150 }, "fertility") const deaths = makeFlow({ x: 160, y: 160 }, "deaths", people.id, sink.id)
const lifeExpectancy = makeConverter({ x: 101, y: -150 }, "life expectancy")
return model( return model(
"Population", "Population",
[source, people, sink, births, deaths, fertility, lifeExpectancy], [source, people, sink, births, deaths, fertility, lifeExpectancy],
@@ -164,13 +163,13 @@ function population(): Model {
* the balancing loop without sitting on any cycle. * the balancing loop without sitting on any cycle.
*/ */
function limitsToGrowth(): Model { function limitsToGrowth(): Model {
const source = makeCloud({ x: -300, y: 0 }) const source = makeCloud({ x: -280, y: 0 })
const yeast = makeStock({ x: 40, y: 0 }, "Yeast") const yeast = makeStock({ x: 40, y: 0 }, "Yeast")
const growth = makeFlow(midpoint(source.position, yeast.position), "growth", source.id, yeast.id) const growth = makeFlow(midpoint(source.position, yeast.position), "growth", source.id, yeast.id)
// crowding rides above the pipe; carrying capacity sits to its right so the // crowding rides above the pipe; carrying capacity sits to its right so the
// `capacity → crowding` link is a short horizontal hop along the top. // `capacity → crowding` link is a short horizontal hop along the top.
const crowding = makeConverter({ x: -40, y: -150 }, "crowding") const crowding = makeConverter({ x: -40, y: -160 }, "crowding")
const capacity = makeConverter({ x: 170, y: -150 }, "carrying capacity") const capacity = makeConverter({ x: 160, y: -160 }, "carrying capacity")
return model( return model(
"Limits to growth", "Limits to growth",
[source, yeast, growth, crowding, capacity], [source, yeast, growth, crowding, capacity],
@@ -275,7 +274,7 @@ function epidemic(): Model {
infected.id, infected.id,
recovered.id, recovered.id,
) )
const infectivity = makeConverter({ x: -140, y: -150 }, "infectivity") const infectivity = makeConverter({ x: -140, y: -160 }, "infectivity")
return model( return model(
"Epidemic", "Epidemic",
[susceptible, infected, recovered, infection, recovery, infectivity], [susceptible, infected, recovered, infection, recovery, infectivity],
@@ -304,7 +303,7 @@ function tragedyOfTheCommons(): Model {
// Two symmetric herds: cattle enter from a Source on the outside, grass leaves // Two symmetric herds: cattle enter from a Source on the outside, grass leaves
// the Pasture downward to a Sink. The two `Pasture → growth` links are the weak // the Pasture downward to a Sink. The two `Pasture → growth` links are the weak
// brake the trap overruns. // brake the trap overruns.
const sourceA = makeCloud({ x: -620, y: 0 }) const sourceA = makeCloud({ x: -640, y: 0 })
const herdA = makeStock({ x: -360, y: 0 }, "Herd A") const herdA = makeStock({ x: -360, y: 0 }, "Herd A")
const growthA = makeFlow( const growthA = makeFlow(
midpoint(sourceA.position, herdA.position), midpoint(sourceA.position, herdA.position),
@@ -312,14 +311,14 @@ function tragedyOfTheCommons(): Model {
sourceA.id, sourceA.id,
herdA.id, herdA.id,
) )
const sinkA = makeCloud({ x: -180, y: 220 }) const sinkA = makeCloud({ x: -200, y: 240 })
const grazingA = makeFlow( const grazingA = makeFlow(
midpoint(pasture.position, sinkA.position), midpoint(pasture.position, sinkA.position),
"grazing A", "grazing A",
pasture.id, pasture.id,
sinkA.id, sinkA.id,
) )
const sourceB = makeCloud({ x: 620, y: 0 }) const sourceB = makeCloud({ x: 640, y: 0 })
const herdB = makeStock({ x: 360, y: 0 }, "Herd B") const herdB = makeStock({ x: 360, y: 0 }, "Herd B")
const growthB = makeFlow( const growthB = makeFlow(
midpoint(sourceB.position, herdB.position), midpoint(sourceB.position, herdB.position),
@@ -327,7 +326,7 @@ function tragedyOfTheCommons(): Model {
sourceB.id, sourceB.id,
herdB.id, herdB.id,
) )
const sinkB = makeCloud({ x: 180, y: 220 }) const sinkB = makeCloud({ x: 200, y: 240 })
const grazingB = makeFlow( const grazingB = makeFlow(
midpoint(pasture.position, sinkB.position), midpoint(pasture.position, sinkB.position),
"grazing B", "grazing B",
@@ -362,7 +361,7 @@ function escalation(): Model {
// Source to its arsenal. The two cross-coupling links span the open centre and // Source to its arsenal. The two cross-coupling links span the open centre and
// cross there, where the R badge lands, so the whole loop reads at a glance. // cross there, where the R badge lands, so the whole loop reads at a glance.
const blueSource = makeCloud({ x: -560, y: -120 }) const blueSource = makeCloud({ x: -560, y: -120 })
const blueArsenal = makeStock({ x: 300, y: -120 }, "Blue arsenal") const blueArsenal = makeStock({ x: 280, y: -120 }, "Blue arsenal")
const blueBuildup = makeFlow( const blueBuildup = makeFlow(
midpoint(blueSource.position, blueArsenal.position), midpoint(blueSource.position, blueArsenal.position),
"Blue buildup", "Blue buildup",
@@ -370,7 +369,7 @@ function escalation(): Model {
blueArsenal.id, blueArsenal.id,
) )
const redSource = makeCloud({ x: -560, y: 120 }) const redSource = makeCloud({ x: -560, y: 120 })
const redArsenal = makeStock({ x: 300, y: 120 }, "Red arsenal") const redArsenal = makeStock({ x: 280, y: 120 }, "Red arsenal")
const redBuildup = makeFlow( const redBuildup = makeFlow(
midpoint(redSource.position, redArsenal.position), midpoint(redSource.position, redArsenal.position),
"Red buildup", "Red buildup",