From df03012c8b69cfae44d11410dd7c9e32444824a9 Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Sat, 20 Jun 2026 11:19:37 +0200 Subject: [PATCH] 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. --- src/model/samples.ts | 59 ++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/src/model/samples.ts b/src/model/samples.ts index a239755..ea97386 100644 --- a/src/model/samples.ts +++ b/src/model/samples.ts @@ -68,9 +68,9 @@ function model(name: string, nodes: ModelNode[], infoLinks: InformationLink[]): * feedback: it just shows the substrate (Stock, Flow, Source, Sink). */ function bathtub(): Model { - const source = makeCloud({ x: -278, y: -18 }) - const water = makeStock({ x: -48, y: -20 }, "Water") - const sink = makeCloud({ x: 242, y: -18 }) + const source = makeCloud({ x: -280, y: 0 }) + const water = makeStock({ x: 0, y: 0 }, "Water") + const sink = makeCloud({ x: 280, y: 0 }) const filling = makeFlow( midpoint(source.position, water.position), "filling", @@ -90,8 +90,8 @@ function savings(): Model { // 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 // visible Reinforcing loop instead of overlapping the inflow pipe. - const source = makeCloud({ x: -250, y: -70 }) - const balance = makeStock({ x: 110, y: 30 }, "Balance") + const source = makeCloud({ x: -240, y: -80 }) + const balance = makeStock({ x: 120, y: 40 }, "Balance") const interest = makeFlow( midpoint(source.position, balance.position), "interest", @@ -108,15 +108,15 @@ function savings(): Model { * (outflow) → Coffee has one `−` → Balancing: it settles toward room temperature. */ function coffee(): Model { - const coffee = makeStock({ x: -200, y: -10 }, "Coffee") - const sink = makeCloud({ x: 190, y: -8 }) + const coffee = makeStock({ x: -200, y: 0 }, "Coffee") + const sink = makeCloud({ x: 200, y: 0 }) const heatLoss = makeFlow( midpoint(coffee.position, sink.position), "heat loss", coffee.id, sink.id, ) - const room = makeConverter({ x: -29, y: -150 }, "room temperature") + const room = makeConverter({ x: 0, y: -160 }, "room temperature") return model( "Coffee cooling", [coffee, sink, heatLoss, room], @@ -131,18 +131,17 @@ function coffee(): Model { * births (`+`), life expectancy lowers deaths (`−`). */ function population(): Model { - const source = makeCloud({ x: -360, y: -8 }) - const people = makeStock({ x: -48, y: -10 }, "Population") - const sink = makeCloud({ x: 250, y: -8 }) - const births = makeFlow( - midpoint(source.position, people.position), - "births", - source.id, - people.id, - ) - const deaths = makeFlow(midpoint(people.position, sink.position), "deaths", people.id, sink.id) - const fertility = makeConverter({ x: -204, y: -150 }, "fertility") - const lifeExpectancy = makeConverter({ x: 101, y: -150 }, "life expectancy") + // A grid-aligned cascade (20px grid): Source and both Converters stack in the left + // column, the Stock sits at the centre, and births → Population → deaths step down + // toward the Sink — so every Information Link lands in open space, not on a pipe. + // Valves are placed by hand, not at the midpoint, to hold the steps. + const source = makeCloud({ x: -360, y: -240 }) + const fertility = makeConverter({ x: -360, y: -40 }, "fertility") + const lifeExpectancy = makeConverter({ x: -360, y: 240 }, "life expectancy") + const people = makeStock({ x: 0, y: 0 }, "Population") + const births = makeFlow({ x: -160, y: -160 }, "births", source.id, people.id) + const sink = makeCloud({ x: 360, y: 240 }) + const deaths = makeFlow({ x: 160, y: 160 }, "deaths", people.id, sink.id) return model( "Population", [source, people, sink, births, deaths, fertility, lifeExpectancy], @@ -164,13 +163,13 @@ function population(): Model { * the balancing loop without sitting on any cycle. */ 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 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 // `capacity → crowding` link is a short horizontal hop along the top. - const crowding = makeConverter({ x: -40, y: -150 }, "crowding") - const capacity = makeConverter({ x: 170, y: -150 }, "carrying capacity") + const crowding = makeConverter({ x: -40, y: -160 }, "crowding") + const capacity = makeConverter({ x: 160, y: -160 }, "carrying capacity") return model( "Limits to growth", [source, yeast, growth, crowding, capacity], @@ -275,7 +274,7 @@ function epidemic(): Model { infected.id, recovered.id, ) - const infectivity = makeConverter({ x: -140, y: -150 }, "infectivity") + const infectivity = makeConverter({ x: -140, y: -160 }, "infectivity") return model( "Epidemic", [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 // the Pasture downward to a Sink. The two `Pasture → growth` links are the weak // 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 growthA = makeFlow( midpoint(sourceA.position, herdA.position), @@ -312,14 +311,14 @@ function tragedyOfTheCommons(): Model { sourceA.id, herdA.id, ) - const sinkA = makeCloud({ x: -180, y: 220 }) + const sinkA = makeCloud({ x: -200, y: 240 }) const grazingA = makeFlow( midpoint(pasture.position, sinkA.position), "grazing A", pasture.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 growthB = makeFlow( midpoint(sourceB.position, herdB.position), @@ -327,7 +326,7 @@ function tragedyOfTheCommons(): Model { sourceB.id, herdB.id, ) - const sinkB = makeCloud({ x: 180, y: 220 }) + const sinkB = makeCloud({ x: 200, y: 240 }) const grazingB = makeFlow( midpoint(pasture.position, sinkB.position), "grazing B", @@ -362,7 +361,7 @@ function escalation(): Model { // 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. 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( midpoint(blueSource.position, blueArsenal.position), "Blue buildup", @@ -370,7 +369,7 @@ function escalation(): Model { blueArsenal.id, ) 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( midpoint(redSource.position, redArsenal.position), "Red buildup",