Physics — simulations
Each simulation is pre-simulated with RK4 at build time — deterministic and replayable — and its parts are ordinary manic entities the whole language composes with. The phase / time / well / energy views are optional and generic: any sim inherits them.
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
pendulum
One pendulum shown four ways from a single deterministic swing: the motion (with a
velocity arrow + KE/PE bars), the phase portrait (θ vs ω), a time series, the
potential-energy well, and energy over time (pendulum + phase/timegraph/
well/energygraph + swing).
// ============================================================================
// pendulum.manic — the physics kit's first sim, seen FOUR ways (Layer 1)
// ----------------------------------------------------------------------------
// `pendulum(id, [center], [length], [angle0], [unit], [damping])` builds a
// pendulum from its physics — PRE-SIMULATED with RK4 at build time
// (deterministic). Only `id` is required. The OPTIONAL, generic view builtins
// render the SAME simulation as math panels and all animate together on `swing`:
// · phase(id,(cx,cy),[size]) — phase portrait (θ vs ω): a closed loop
// · timegraph(id,(cx,cy),[size]) — θ(t) & ω(t) with a sweep line
// · well(id,(cx,cy),[size]) — energy well U(θ), bob = ball in a bowl
// · energygraph(id,(cx,cy),[size]) — KE / PE / total over time
//
// TO ADAPT: add damping (6th arg) and watch the phase loop spiral inward, the
// ball settle to the bottom of the well, and total energy decay — e.g.
// `pendulum(p, (250,220), 1.2, 55, 105, 0.5)`.
// ============================================================================
title("Pendulum — one swing, four views");
canvas("16:9");
text(hdr, (cx, 40), "One pendulum, four views");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
// the physical sim (left), with velocity arrow + KE/PE bars
pendulum(p, (250, 220), 1.2, 55, 105);
untraced(p.path);
// four math views of the SAME simulation, 2×2 on the right
phase(p, (715, 165), 90); // θ vs ω — a closed loop
timegraph(p, (1000, 165), 90); // θ(t), ω(t) with a sweep line
well(p, (715, 455), 90); // U(θ) with the bob as a rolling ball
energygraph(p, (1000, 455), 90); // KE / PE / total over time
text(cap, (cx, h - 30), "sim · phase portrait · time series · energy well · energy over time — all from one pre-simulated swing");
size(cap, 16); color(cap, dim); display(cap);
draw(p.path, 0.8);
swing(p, 10); // every panel animates in lockstep
pendulum-damped
The same four views with friction on (damping): the swing decays, the phase loop
spirals inward, the well ball settles, and the total-energy line drops — dissipation
told the same way by every panel.
// ============================================================================
// pendulum-damped.manic — the SAME four views, now with friction
// ----------------------------------------------------------------------------
// Identical to `pendulum.manic` but with `damping` (the 6th arg) turned on, so
// the swing loses energy. Watch every panel respond together:
// · sim — the amplitude shrinks each swing (slows toward rest)
// · phase — the closed loop becomes an INWARD SPIRAL
// · well — the ball SETTLES toward the bottom of the bowl
// · energygraph — the TOTAL (gold) line DECAYS (KE↔PE trade, sum drops)
//
// Set damping = 0 (or drop the 6th arg) for the frictionless version, where the
// loop stays closed and the total-energy line stays flat (conservation).
// ============================================================================
title("Damped pendulum — energy bleeds away");
canvas("16:9");
text(hdr, (cx, 40), "Add friction: the swing decays, and every view follows");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
// the physical sim (left) — 6th arg 0.6 is the damping
pendulum(p, (250, 220), 1.2, 55, 105, 0.6);
untraced(p.path);
// the same four math views of the SAME (now damped) simulation
phase(p, (715, 165), 90); // loop → inward spiral
timegraph(p, (1000, 165), 90); // θ(t), ω(t) — envelope shrinks
well(p, (715, 455), 90); // ball settles to the bottom
energygraph(p, (1000, 455), 90); // total energy decays
text(cap, (cx, h - 30), "damping = 0.6 · the phase spiral, settling ball, and decaying total energy all show the same loss");
size(cap, 16); color(cap, dim); display(cap);
draw(p.path, 0.8);
swing(p, 10);
pendulum-annotated
A guided anatomy lesson proving physics composes with base manic: section chapters,
text / arrow / bracelabel annotations, and show/recolor/flash/pulse all
driving the sim’s parts — no special physics mode.
// ============================================================================
// pendulum-annotated.manic — a guided lesson: physics + base manic compose
// ----------------------------------------------------------------------------
// Proof that a sim's parts (`{id}.pivot/.rod/.bob/.path/…`) are ORDINARY manic
// entities. This whole lesson is built from BASE std vocabulary — `section`
// chapters, `text` labels, leader `arrow`s, a reference `line`, a `bracelabel`,
// and `show`/`fade`/`recolor`/`flash`/`pulse`/`draw`/`say` — wrapped around the
// physics `pendulum` + `energygraph` view + `swing`. No special "physics mode":
// every std verb/modifier/annotation addresses the physics entities directly.
// ============================================================================
title("Anatomy of a Pendulum");
canvas("16:9");
text(hdr, (cx, 44), "Anatomy of a Pendulum");
size(hdr, 30); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
// ---- the physics sim + one math view (parts start hidden; path untraced) ----
pendulum(p, (440, 210), 1.6, 50, 110);
hidden(p.pivot); hidden(p.rod); hidden(p.bob); hidden(p.overlays);
untraced(p.path); // trace 0 but keep opacity (reveal later)
energygraph(p, (1030, 250), 105); // built now (its sweep joins `swing`)
hidden(p.energy);
// ---- base annotations, all hidden to reveal in order ----
line(vref, (440, 210), (440, 386)); // vertical reference
color(vref, dim); stroke(vref, 2); hidden(vref);
bracelabel(Lb, (440, 210), (575, 323), "L = 1.6 m", 26); // rod length
color(Lb, gold); hidden(Lb); hidden(Lb.label);
text(pivL, (330, 200), "pivot (fixed)"); size(pivL, 18); color(pivL, dim); display(pivL); hidden(pivL);
arrow(pivA, (388, 205), (428, 209)); color(pivA, dim); stroke(pivA, 2); hidden(pivA);
text(bobL, (600, 355), "bob — mass m"); size(bobL, 18); color(bobL, magenta); display(bobL); hidden(bobL);
arrow(bobA, (628, 345), (585, 330)); color(bobA, dim); stroke(bobA, 2); hidden(bobA);
text(angL, (472, 300), "θ₀ = 50°"); size(angL, 18); color(angL, lime); display(angL); hidden(angL);
text(cap, (cx, h - 42), ""); size(cap, 20); color(cap, dim); display(cap);
// ============================== SCRIPT ==============================
show(hdr, 0.5);
section("Anatomy");
say(cap, "a fixed pivot …", 0.4);
show(p.pivot, 0.4); pulse(p.pivot); show(pivL, 0.4); draw(pivA, 0.3);
wait(0.5);
say(cap, "… a rigid rod of length L …", 0.4);
show(p.rod, 0.4); show(Lb, 0.4); show(Lb.label, 0.4);
wait(0.6);
say(cap, "… and a bob of mass m at the end", 0.4);
show(p.bob, 0.5); recolor(p.bob, magenta, 0.3); pulse(p.bob); show(bobL, 0.4); draw(bobA, 0.3);
wait(0.5);
say(cap, "all one group — a base broadcast flashes every part at once", 0.4);
flash(p.parts, lime); // std broadcast over the whole sim
wait(0.7);
section("Release");
say(cap, "held at θ₀ from the vertical, then let go", 0.4);
show(vref, 0.4); show(angL, 0.4); flash(p.rod, cyan);
wait(0.9);
// clear the static annotations before the motion
fade(Lb, 0.3); fade(Lb.label, 0.3); fade(pivA, 0.3); fade(bobA, 0.3);
fade(pivL, 0.3); fade(bobL, 0.3); fade(angL, 0.3); fade(vref, 0.3);
wait(0.3);
section("Swing");
say(cap, "gravity pulls it back — velocity arrow + energy bars ride along", 0.4);
show(p.overlays, 0.4); // reveal the velocity arrow + KE/PE bars
draw(p.path, 0.8); // trace the arc it will follow
section("Energy");
say(cap, "and the SAME swing, read as energy over time", 0.4);
show(p.energy, 0.5); // reveal the energy graph, then run it all
swing(p, 10); // every panel + annotation-free scene animates
spring
A mass on a spring (simple harmonic motion) drawn with a real stretching coil — the
same generic views on a different system; note the energy well is a parabola
(½kx²) rather than the pendulum’s cosine (spring + the views + run).
// ============================================================================
// spring.manic — the physics kit's SECOND sim (Layer 1)
// ----------------------------------------------------------------------------
// `spring(id, [center], [stiffness], [x0], [unit], [damping])` — a mass on a
// spring, PRE-SIMULATED with RK4 (deterministic). Only `id` is required. It's a
// different system from the pendulum, yet it inherits the SAME generic views for
// free — note the energy well here is a PARABOLA U(x)=½kx² (the pendulum's is a
// cosine). `run(id, [dur])` replays the motion (alias: `swing`).
//
// TO ADAPT: add damping (6th arg) — the phase ellipse spirals in, the ball
// settles to the bottom of the parabola, and total energy decays:
// `spring(s, (360,320), 10, 1.4, 110, 0.6)`.
// ============================================================================
title("Mass on a spring — four views");
canvas("16:9");
text(hdr, (cx, 44), "A different sim — same four views (well is a parabola)");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
// the mass–spring on the left
spring(s, (330, 300), 10, 1.4, 110);
// the same generic views of the SAME simulation, 2×2 on the right
phase(s, (760, 175), 90); // x vs v — an ellipse
timegraph(s, (1010, 175), 90); // x(t), v(t) with a sweep line
well(s, (760, 460), 90); // parabolic energy well U(x)=½kx²
energygraph(s, (1010, 460), 90); // KE / PE / total over time
text(cap, (cx, h - 32), "one mass–spring, seen as motion · phase ellipse · time series · parabolic well · energy");
size(cap, 16); color(cap, dim); display(cap);
run(s, 10); // every panel animates in lockstep
spring-damped
The damped spring: the coil’s oscillation decays, the phase ellipse spirals in, the ball settles in the parabola, and total energy bleeds away.
// ============================================================================
// spring-damped.manic — the same four views, now with friction
// ----------------------------------------------------------------------------
// Identical to `spring.manic` but with `damping` (the 6th arg) turned on, so the
// oscillation loses energy. Watch every panel respond together:
// · sim — the coil's swing SHRINKS each cycle (settles to rest)
// · phase — the ellipse becomes an INWARD SPIRAL
// · well — the ball SETTLES to the bottom of the parabola U(x)=½kx²
// · energygraph — the TOTAL (gold) line DECAYS (KE↔PE trade, sum drops)
//
// Set damping = 0 (or drop the 6th arg) for the frictionless version, where the
// ellipse stays closed and the total-energy line stays flat (conservation).
// ============================================================================
title("Damped spring — energy bleeds away");
canvas("16:9");
text(hdr, (cx, 44), "Add friction: the oscillation decays, every view follows");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
// the mass–spring (left) — 6th arg 0.6 is the damping
spring(s, (330, 300), 10, 1.4, 110, 0.6);
// the same generic views of the SAME (now damped) simulation
phase(s, (760, 175), 90); // ellipse → inward spiral
timegraph(s, (1010, 175), 90); // x(t), v(t) — envelope shrinks
well(s, (760, 460), 90); // ball settles to the bottom of the parabola
energygraph(s, (1010, 460), 90); // total energy decays
text(cap, (cx, h - 32), "damping = 0.6 · the spiral, settling ball, and decaying total energy all show the same loss");
size(cap, 16); color(cap, dim); display(cap);
run(s, 10);
spring-annotated
Elevating the spring with a TYPEWRITER lab-note (type + cursor) and LIVE COUNTERS
(counter + to(_, value, …)) ticking k and the period up — Hooke’s law → parabolic
well → SHM, with no stage-covering section cards. One of three elevation styles.
// ============================================================================
// spring-annotated.manic — ELEVATE a sim: typewriter narration + live data
// ----------------------------------------------------------------------------
// One elevation flavour of several (see pulley-annotated for camera work,
// brachistochrone-annotated for kinetic typography). Here the story is told by
// a TYPEWRITER lab-note (`type` + `cursor`) and LIVE COUNTERS (`counter` +
// `to(_, value, …)`) that tick up — no stage-covering section cards, so the
// motion is never hidden. The spring's parts are ordinary entities the whole
// language drives directly.
// ============================================================================
title("Anatomy of a Spring — Hooke's Law & SHM");
canvas("16:9");
text(hdr, (cx, 40), "Anatomy of a Spring"); size(hdr, 28); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
// ---- the sim + views (staged) ----
spring(sp, (340, 320), 10, 1.4, 105);
hidden(sp.wall); hidden(sp.spring); hidden(sp.mass); hidden(sp.overlays);
untraced(sp.path);
well(sp, (1015, 230), 112); hidden(sp.well);
energygraph(sp, (1015, 480), 112); hidden(sp.energy);
// ---- part annotations ----
line(eq, (340, 268), (340, 372)); color(eq, dim); stroke(eq, 2); untraced(eq);
text(coilL, (285, 232), "spring, stiffness k"); size(coilL, 16); color(coilL, lime); display(coilL); hidden(coilL);
text(massL, (520, 272), "mass m"); size(massL, 16); color(massL, cyan); display(massL); hidden(massL);
bracelabel(xb, (340, 372), (487, 372), "x₀", 22); color(xb, gold); hidden(xb); hidden(xb.label);
text(hooke, (340, 168), "F = −k·x"); size(hooke, 24); color(hooke, gold); display(hooke); hidden(hooke);
// ---- a TYPEWRITER lab-note with a live cursor (the narration device) ----
text(note, (cx, h - 44), ""); size(note, 21); color(note, fg); display(note); cursor(note);
text(kick, (96, 92), ""); size(kick, 17); color(kick, magenta); bold(kick); display(kick);
// ---- LIVE COUNTERS (tick up on reveal) ----
counter(kC, (150, 470), 0, 1, "k = ", " N/m"); size(kC, 26); color(kC, lime); display(kC); hidden(kC);
counter(tC, (150, 512), 0, 2, "T = ", " s"); size(tC, 26); color(tC, cyan); display(tC); hidden(tC);
// ============================== SCRIPT ==============================
show(hdr, 0.5);
say(kick, "SETUP", 0.2);
say(note, "a wall, a coil of stiffness k, and a mass", 0.1); type(note, 1.4);
show(sp.wall, 0.4); pulse(sp.wall); show(coilL, 0.4);
show(sp.spring, 0.4); flash(sp.spring, lime); show(sp.mass, 0.4); pulse(sp.mass); show(massL, 0.4);
show(kC, 0.3); to(kC, value, 10, 0.8);
wait(0.5);
say(kick, "HOOKE'S LAW", 0.2);
say(note, "pull it x₀ from rest — it pulls back, F = −k·x", 0.1); type(note, 1.7);
draw(eq, 0.4); show(xb, 0.4); show(xb.label, 0.4); show(hooke, 0.4); flash(sp.spring, gold);
show(tC, 0.3); to(tC, value, 1.99, 0.9); // period 2π√(m/k)
wait(0.6);
fade(coilL, 0.3); fade(massL, 0.3); fade(xb, 0.3); fade(xb.label, 0.3); fade(hooke, 0.3);
wait(0.2);
say(kick, "MOTION", 0.2);
say(note, "release — simple harmonic motion; every panel tells the same swing", 0.1); type(note, 1.9);
show(sp.well, 0.5); show(sp.overlays, 0.4); show(sp.energy, 0.5); draw(sp.path, 0.6);
run(sp, 10);
spring-paper
The SAME spring sim dressed as a textbook figure AND run: template("paper") inks it,
a hatched support wall, a forest-green coil and outlined mass box, Hooke’s law and x₀
revealed, then run plays the SHM — the paper treatment on a LIVE sim (see pulley-paper).
// ============================================================================
// spring-paper.manic — an ANIMATED sim in textbook paper style
// ----------------------------------------------------------------------------
// The `spring` sim (mass on a spring, SHM), dressed as a textbook figure AND run:
// `template("paper")` inks it automatically, a hatched `support` wall, the coil
// in forest green, an outlined mass box, plus a base-manic reveal (Hooke's law,
// equilibrium, x₀) — then `run` plays the oscillation. The companion to
// pulley-paper: the paper/support treatment on another live sim.
// ============================================================================
title("Mass on a spring — textbook style");
canvas("16:9");
template("paper");
text(hdr, (cx, 46), "Mass on a spring — Hooke's law"); color(hdr, fg); size(hdr, 26); bold(hdr); display(hdr); hidden(hdr);
// the sim, restyled to textbook ink (parts hidden, revealed in order)
spring(sp, (380, 300), 10, 1.3, 110);
outlined(sp.mass); outline(sp.mass, fg);
hidden(sp.wall); hidden(sp.spring); hidden(sp.mass); hidden(sp.overlays);
untraced(sp.path);
// a hatched wall stands in for the sim's plain anchor; equilibrium reference
support(wall, (177, 300), 150, "right"); untraced(wall);
line(eq, (380, 246), (380, 354)); color(eq, dim); stroke(eq, 2); untraced(eq);
text(eqL, (380, 232), "equilibrium"); color(eqL, dim); size(eqL, 15); display(eqL); hidden(eqL);
text(coilL, (300, 214), "spring, stiffness k"); color(coilL, fg); size(coilL, 17); display(coilL); hidden(coilL);
text(massL, (548, 258), "mass m"); color(massL, fg); size(massL, 17); display(massL); hidden(massL);
bracelabel(xb, (380, 360), (523, 360), "x₀", 22); color(xb, fg); hidden(xb); hidden(xb.label);
text(hooke, (380, 152), "F = −k·x"); color(hooke, fg); size(hooke, 24); display(hooke); hidden(hooke);
text(cap, (cx, h - 40), ""); color(cap, fg); size(cap, 20); display(cap);
// ============================== SCRIPT ==============================
show(hdr, 0.5);
say(cap, "a coil of stiffness k fixed to a wall, with a mass on the end", 0.4);
draw(wall, 0.4); show(sp.spring, 0.4); show(coilL, 0.4); show(sp.mass, 0.4); show(massL, 0.3);
wait(0.4);
say(cap, "pull it x₀ from equilibrium — it pulls straight back, F = −k·x", 0.4);
draw(eq, 0.4); show(eqL, 0.3); show(xb, 0.4); show(xb.label, 0.4); show(hooke, 0.5);
wait(0.7);
fade(coilL, 0.3); fade(massL, 0.3); fade(xb, 0.3); fade(xb.label, 0.3); fade(hooke, 0.3);
say(cap, "release — simple harmonic motion", 0.4);
run(sp, 9);
double-pendulum
Deterministic chaos: two arms hinged end-to-end whose outer bob traces a wild,
unrepeatable curve — yet the render is frame-identical every run. A 4-D system, so
it shows phase (θ₁ vs θ₂) and energygraph but has no potential well
(doublependulum + views + run).
// ============================================================================
// double-pendulum.manic — chaos, on the physics baseline (Layer 1)
// ----------------------------------------------------------------------------
// `doublependulum(id, [center], [angle1], [angle2], [unit])` — two arms hinged
// end-to-end: deterministic, yet exquisitely sensitive to initial conditions.
// PRE-SIMULATED with RK4 (so the render is frame-identical every run). It's a
// 4-D system, so it inherits `phase` (θ₁ vs θ₂), `timegraph`, and `energygraph`
// — but NOT `well` (there's no single-variable potential). `run(id,[dur])` plays
// it; drawing `{id}.path` in parallel traces the outer bob's chaotic curve.
//
// TO ADAPT: nudge angle2 by one degree and re-run — the trail diverges completely.
// ============================================================================
title("Double pendulum — deterministic chaos");
canvas("16:9");
text(hdr, (cx, 44), "Double pendulum — deterministic, yet unpredictable");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
// the chaotic sim on the left (outer bob traces the wild curve)
doublependulum(dp, (400, 250), 125, 110);
untraced(dp.path);
// the views that DO apply to a 4-D system (no potential well here)
phase(dp, (900, 190), 100); // θ₁ vs θ₂ — the coupled angles
energygraph(dp, (900, 480), 100); // KE / PE / total (total ~conserved)
text(cap, (cx, h - 32), "outer bob's trail is chaotic; total energy stays (nearly) conserved — no friction");
size(cap, 16); color(cap, dim); display(cap);
// trace the chaotic trail AS the pendulum swings (draw + run in parallel)
par {
run(dp, 12);
draw(dp.path, 12);
}
spring-pendulum
An elastic pendulum — a bob on a springy rod (drawn as a stretching coil) that both
swings and bounces, energy sloshing between the two modes (springpendulum).
// spring-pendulum.manic — the elastic pendulum: swings AND bounces.
// springpendulum(id,[center],[angle0],[stretch0],[unit],[damping]) — a bob on a
// springy rod (drawn as a stretching coil). Energy sloshes between the swing and
// the bounce, so the phase portrait and energy graph are richer than a rigid rod.
title("Elastic pendulum — swing meets bounce");
canvas("16:9");
text(hdr,(cx,44),"Spring pendulum — energy sloshes between swing and bounce");
size(hdr,23); color(hdr,cyan); bold(hdr); display(hdr);
springpendulum(sp, (380,230), 35, 0.5, 105);
untraced(sp.path);
phase(sp, (900,190), 95); // θ vs ω
energygraph(sp, (900,470), 95); // KE / PE / total (~conserved, lightly damped)
text(cap,(cx,h-32),"the coil stretches as it swings — a two-mode system"); size(cap,16); color(cap,dim); display(cap);
par { run(sp, 11); draw(sp.path, 11); }
kapitza
The Kapitza pendulum: vibrate the pivot fast enough and the inverted position
becomes stable — the bob hovers near the top instead of falling (kapitza).
// kapitza.manic — vibrate the pivot fast enough and the pendulum stands UP.
// kapitza(id,[center],[angle0deg],[vibeamp],[unit]) — a driven (Kapitza) pendulum;
// with a strong enough vibration the INVERTED position becomes stable.
title("Kapitza pendulum — stable upside-down");
canvas("16:9");
text(hdr,(cx,44),"Kapitza pendulum — fast pivot vibration stabilises 'up'");
size(hdr,23); color(hdr,cyan); bold(hdr); display(hdr);
kapitza(kp, (640,430), 165, 240, 150); // start near inverted, strong vibration
untraced(kp.path);
text(cap,(cx,h-32),"the gold pivot bobs fast; the bob hovers near the top (inverted)"); size(cap,16); color(cap,dim); display(cap);
par { run(kp, 9); draw(kp.path, 9); }
cart-pendulum
A pendulum on a spring-mounted cart rolling on a track — the classic control-theory
system; cart and bob trade momentum and energy (cartpendulum).
// cart-pendulum.manic — a pendulum on a spring-mounted cart (the control classic).
// cartpendulum(id,[center],[angle0deg],[unit]) — cart rolls on a track (spring to a
// wall) while the pendulum swings; the two exchange momentum and energy.
title("Cart-pendulum — coupled motion");
canvas("16:9");
text(hdr,(cx,44),"Cart-pendulum — the cart and bob trade momentum"); size(hdr,23); color(hdr,cyan); bold(hdr); display(hdr);
cartpendulum(cp, (480,330), 55, 105);
phase(cp, (1000,190), 95); // θ vs ω
energygraph(cp, (1000,470), 95); // KE / PE / total (~conserved)
text(cap,(cx,h-32),"cart on a spring + swinging bob — energy stays (nearly) conserved"); size(cap,16); color(cap,dim); display(cap);
run(cp, 11);
compare-pendulum
Sensitive dependence: two identical driven pendulums started 0.001 rad apart drift
onto completely different paths — the butterfly effect, watched in phase/timegraph
(comparependulum).
// compare-pendulum.manic — sensitive dependence: two pendulums 0.001 rad apart.
// comparependulum(id,[center],[angle0deg],[unit]) — identical driven-damped
// physics, a hair-different start — yet they diverge completely (the butterfly effect).
title("Two pendulums, one hair apart");
canvas("16:9");
text(hdr,(cx,44),"Sensitive dependence — a 0.001 rad difference explodes"); size(hdr,23); color(hdr,cyan); bold(hdr); display(hdr);
comparependulum(cm, (380,230), 12, 120);
phase(cm, (900,190), 95); // θ_A vs θ_B — starts on the diagonal, then scatters
timegraph(cm, (900,470), 95); // θ_A(t) & θ_B(t) diverge
text(cap,(cx,h-32),"cyan and magenta start together, then go their separate ways"); size(cap,16); color(cap,dim); display(cap);
run(cm, 13);
vertical-spring
A mass bobbing on a vertical spring under gravity — gravity shifts the equilibrium
but the energy well stays a parabola (verticalspring).
// vertical-spring.manic — a mass bobbing on a vertical spring under gravity.
title("Vertical spring — bobbing under gravity");
canvas("16:9");
text(hdr,(cx,44),"Vertical spring — a mass bobs about its stretched equilibrium"); size(hdr,22); color(hdr,cyan); bold(hdr); display(hdr);
verticalspring(vs, (400,170), 0.7);
untraced(vs.path);
phase(vs, (900,190), 95); // d vs ḋ
well(vs, (900,470), 95); // parabolic well (shifted by gravity)
text(cap,(cx,h-32),"gravity shifts the equilibrium down; the well is still a parabola"); size(cap,16); color(cap,dim); display(cap);
par { run(vs, 10); draw(vs.path, 10); }
spring-incline
A mass on a spring on an inclined plane; gravity’s along-ramp component sets a new
stretched rest point it oscillates about (springincline).
// spring-incline.manic — a mass on a spring on an inclined plane.
title("Spring on an incline");
canvas("16:9");
text(hdr,(cx,44),"Spring on an incline — gravity's along-ramp pull shifts equilibrium"); size(hdr,22); color(hdr,cyan); bold(hdr); display(hdr);
springincline(si, (360,180), 32);
untraced(si.path);
phase(si, (960,200), 100);
energygraph(si, (960,470), 100);
text(cap,(cx,h-32),"the bob oscillates along the ramp about its stretched rest point"); size(cap,16); color(cap,dim); display(cap);
par { run(si, 10); draw(si.path, 10); }
bungee
A bungee jump: free-fall, then a ONE-SIDED elastic cord (it only pulls) catches and
bounces the jumper — note the lopsided energy well (bungee).
// bungee.manic — free-fall, then a one-sided elastic cord catches the jumper.
title("Bungee jump — free-fall, then bounce");
canvas("16:9");
text(hdr,(cx,44),"Bungee — the cord only PULLS: free-fall, then an elastic bounce"); size(hdr,22); color(hdr,cyan); bold(hdr); display(hdr);
bungee(bg, (640,110));
phase(bg, (1000,200), 95); // asymmetric well shows in the phase loop
well(bg, (1000,470), 95); // linear (fall) → parabola (cord): a lopsided well
text(cap,(cx,h-32),"note the lopsided energy well — flat during free-fall, steep once the cord bites"); size(cap,15); color(cap,dim); display(cap);
run(bg, 11);
resonance
A driven spring pushed near its natural frequency √(k/m): the amplitude climbs and
climbs — resonance, watched building up in phase/energygraph (resonance).
// resonance.manic — a driven spring pumped near its natural frequency.
title("Resonance — driving near the natural frequency");
canvas("16:9");
text(hdr,(cx,44),"Resonance — drive near √(k/m) and the amplitude grows and grows"); size(hdr,22); color(hdr,cyan); bold(hdr); display(hdr);
resonance(rs, (400,300), 3.8); // natural freq = √(16/1) = 4; drive 3.8 ≈ resonance
untraced(rs.path);
phase(rs, (960,200), 100); // spiral OUT to a big steady-state loop
energygraph(rs, (960,470), 100); // energy climbs, then plateaus
text(cap,(cx,h-32),"drive frequency 3.8 vs natural 4.0 — near resonance, so it builds up big"); size(cap,15); color(cap,dim); display(cap);
par { run(rs, 12); draw(rs.path, 12); }
double-spring
Two masses coupled by springs between walls — push one and the energy sloshes back
and forth (beating); normal modes show as diagonals in phase (doublespring).
// double-spring.manic — two masses coupled by springs: energy sloshes (beating).
title("Coupled springs — energy sloshes back and forth");
canvas("16:9");
text(hdr,(cx,44),"Two coupled masses — push one, and the energy beats between them"); size(hdr,22); color(hdr,cyan); bold(hdr); display(hdr);
doublespring(dd, (430,300), 85);
phase(dd, (980,200), 100); // x1 vs x2 — normal modes are diagonals
energygraph(dd, (980,470), 100);
text(cap,(cx,h-32),"block 1 (cyan) starts displaced; watch block 2 (magenta) pick up its swing"); size(cap,15); color(cap,dim); display(cap);
run(dd, 12);
series-parallel-springs
The same mass on springs in series (soft, slow) vs parallel (stiff, fast), side by
side — the timegraph makes the frequency difference obvious (seriesparallel).
// series-parallel-springs.manic — same mass, springs in series vs parallel.
title("Series vs parallel springs");
canvas("16:9");
text(hdr,(cx,44),"Same mass, same springs — series is soft (slow), parallel is stiff (fast)"); size(hdr,21); color(hdr,cyan); bold(hdr); display(hdr);
seriesparallel(sp, (560,150), 68);
timegraph(sp, (1060,320), 95); // y_s(t) vs y_p(t): parallel oscillates faster
text(cap,(cx,h-30),"parallel adds stiffness (k₁+k₂); series divides it (1/k = 1/k₁+1/k₂)"); size(cap,15); color(cap,dim); display(cap);
run(sp, 11);
car-suspension
A quarter-car riding a scrolling road — a speed bump, a washboard stretch, and a
pothole — its spring+damper soaking up the ride (carsuspension).
// car-suspension.manic — a quarter-car riding a road: bump, washboard, pothole.
title("Car suspension — riding the road");
canvas("16:9");
text(hdr,(cx,44),"Quarter-car suspension — the body bobs as the wheel rides the road"); size(hdr,22); color(hdr,cyan); bold(hdr); display(hdr);
carsuspension(car, (640,430));
energygraph(car, (1050,170), 100);
text(cap,(cx,h-30),"a speed bump, a washboard stretch, then a pothole — the spring+damper soak it up"); size(cap,15); color(cap,dim); display(cap);
run(car, 12);
car-suspension-annotated
A marketing hero: the quarter-car suspension on a template("paper") brochure page,
elevated with generic base-manic — a live counter (sprung mass), leader-arrow
callouts, and an energygraph of the shock being absorbed — riding a scrolling road.
// ============================================================================
// car-suspension-annotated.manic — a marketing hero, paper (brochure) style
// ----------------------------------------------------------------------------
// The `carsuspension` quarter-car sim, dressed as a clean product diagram on a
// `template("paper")` page and ELEVATED with generic base-manic: a live
// `counter` (the sprung mass ticking up), leader-`arrow` callouts, a
// `bracelabel` for the travel, and a compact `energygraph` showing the shock
// being absorbed — then it rides a scrolling road (bump · washboard · pothole).
// Every part of the sim is an ordinary entity, so it all composes.
// ============================================================================
title("Car suspension — smooth over every bump");
canvas("16:9");
template("paper");
// ---- hero title ----
text(hdr, (cx, 52), "Car suspension: how it soaks up the road"); color(hdr, fg); size(hdr, 30); bold(hdr); display(hdr); hidden(hdr);
text(sub, (cx, 88), "the quarter-car model — sprung mass · spring + damper · wheel"); color(sub, dim); size(sub, 18); display(sub); hidden(sub);
// ---- the sim, recoloured for a bold, clean brochure look ----
carsuspension(car, (430, 430));
color(car.body, blue); // the car body — bold accent
color(car.road, fg); // the road — dark ink
hidden(car); // reveal the whole sim (road, wheel, spring, body, energy) together
// a compact energy panel (top-right) — the suspension absorbing the shock
energygraph(car, (1055, 215), 88);
// ---- generic-kit callouts (leader arrows + labels), hidden to reveal in order ----
text(cb, (640, 296), "car body — the sprung mass"); color(cb, blue); size(cb, 18); display(cb); hidden(cb);
arrow(ab, (636, 302), (478, 320)); color(ab, dim); stroke(ab, 2); hidden(ab);
text(csp, (610, 382), "spring + damper"); color(csp, lime); size(csp, 18); display(csp); hidden(csp);
arrow(asp, (606, 384), (448, 382)); color(asp, dim); stroke(asp, 2); hidden(asp);
text(cw, (620, 472), "wheel — the unsprung mass"); color(cw, gold); size(cw, 18); display(cw); hidden(cw);
arrow(aw, (616, 470), (448, 434)); color(aw, dim); stroke(aw, 2); hidden(aw);
text(cr, (cx, 648), "the road: a bump, a washboard stretch, then a pothole"); color(cr, fg); size(cr, 17); display(cr); hidden(cr);
// a live spec counter (generic kit) — the sprung mass ticking up
counter(mc, (150, 320), 0, 0, "m = ", " kg"); color(mc, fg); size(mc, 30); display(mc); hidden(mc);
text(spec, (cx, h - 28), "quarter-car · k = 20 kN/m · c = 4 kN·s/m · v = 8 m/s"); color(spec, dim); size(spec, 16); display(spec); hidden(spec);
// ============================== SCRIPT ==============================
show(hdr, 0.5); show(sub, 0.4);
wait(0.3);
show(car, 0.6);
wait(0.3);
show(cb, 0.3); draw(ab, 0.3);
show(csp, 0.3); draw(asp, 0.3);
show(cw, 0.3); draw(aw, 0.3);
show(cr, 0.3);
show(mc, 0.3); to(mc, value, 500, 0.9); // the sprung mass counts up
show(spec, 0.4);
wait(0.5);
// the ride — the road scrolls, the body glides while the wheel tracks every bump
run(car, 11);
piston
An engine piston: a spinning crank + connecting rod turn rotation into the piston’s
up-and-down stroke — the slider-crank mechanism (piston).
// piston.manic — an engine piston: a spinning crank drives a slider in a cylinder.
title("Engine piston — the slider-crank");
canvas("16:9");
text(hdr,(cx,46),"Slider-crank — a spinning crank becomes up-and-down motion"); size(hdr,23); color(hdr,cyan); bold(hdr); display(hdr);
piston(eng, (cx, 480), 60);
text(cap,(cx,h-32),"gold crank sweeps the circle; the rod converts it to the piston's stroke"); size(cap,16); color(cap,dim); display(cap);
run(eng, 9);
molecule
A molecule as balls and springs — atoms bonded on every side, vibrating about their
equilibrium shape with the total energy conserved (molecule).
// molecule.manic — atoms bonded by springs, vibrating about their shape.
title("Vibrating molecule");
canvas("16:9");
text(hdr,(cx,46),"A molecule as balls and springs — bonds stretch and the atoms jiggle"); size(hdr,22); color(hdr,cyan); bold(hdr); display(hdr);
molecule(mol, (520, 300), 3);
energygraph(mol, (1000, 300), 110); // total energy is conserved as it vibrates
text(cap,(cx,h-32),"one atom starts pulled out; the bonds pull it back and the whole thing rings"); size(cap,15); color(cap,dim); display(cap);
run(mol, 11);
robot-arm
A two-link robot arm reaching for a target: the joint rates come from the analytic
inverse Jacobian, so the arm drives its end-effector to the goal and settles there —
inverse kinematics as a solved motion (robotarm).
// ============================================================================
// robot-arm.manic — inverse kinematics, on the physics baseline (Layer 1)
// ----------------------------------------------------------------------------
// `robotarm(id, [center], [mode], [unit])` — a two-link arm that tracks a target
// by inverse kinematics. Its joint rates are the analytic 2×2 inverse Jacobian
// times a gain on the end-effector error, so the gripper chases the target.
// `mode` 1 = trace a circle (default), 2 = figure-8, 0 = reach a fixed point and
// settle. PRE-SIMULATED with RK4; `run(id,[dur])` replays it, and `{id}.path`
// traces the route the gripper sweeps.
//
// TO ADAPT: set mode 2 for a figure-8, or mode 0 to reach one fixed point.
// ============================================================================
title("Robot arm — tracking by inverse kinematics");
canvas("16:9");
text(hdr, (cx, 46), "Two-link arm — inverse kinematics keeps the gripper on the moving target");
size(hdr, 22); color(hdr, cyan); bold(hdr); display(hdr);
robotarm(rb, (cx, 470), 1);
text(cap, (cx, h - 32), "gold shoulder + cyan forearm chase the lime ring around its circle — the gripper traces the path");
size(cap, 15); color(cap, dim); display(cap);
run(rb, 10);
pulley
The Atwood machine: two masses over one pulley, the heavier one accelerating down at
(m₁−m₂)g/(m₁+m₂). energygraph shows kinetic energy climbing as potential falls
(pulley).
// ============================================================================
// pulley.manic — the Atwood machine, on the physics baseline (Layer 1)
// ----------------------------------------------------------------------------
// `pulley(id, [center], [m1], [m2], [unit])` — two masses over one pulley. The
// heavier one accelerates down at a = (m₁−m₂)g/(m₁+m₂). PRE-SIMULATED with RK4;
// `run(id,[dur])` replays it. `energygraph` shows the KE↔PE trade as the system
// speeds up (total is conserved — no friction).
//
// TO ADAPT: set m1 and m2 closer together for a gentler acceleration.
// ============================================================================
title("Atwood machine — unequal masses accelerate");
canvas("16:9");
text(hdr, (cx, 44), "Atwood machine — a = (m₁−m₂)g / (m₁+m₂)");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
pulley(pl, (440, 170), 3, 2);
energygraph(pl, (960, 330), 120);
text(cap, (cx, h - 32), "cyan m₁ (heavier) sinks, magenta m₂ rises; kinetic energy climbs as potential falls");
size(cap, 16); color(cap, dim); display(cap);
run(pl, 5);
pulley-scale
The surprise every physics class remembers: an in-line spring scale on an Atwood
machine reads the rope TENSION 2·m₁·m₂·g/(m₁+m₂) — not the sum of the two weights
(pulleyscale).
// ============================================================================
// pulley-scale.manic — what does the scale read? (physics Layer 1)
// ----------------------------------------------------------------------------
// `pulleyscale(id, [center], [m1], [m2], [unit])` — an Atwood machine over two
// pulleys with a spring scale in the rope between them. The classic surprise:
// the scale reads the rope TENSION 2·m₁·m₂·g/(m₁+m₂), NOT the sum of the two
// weights. PRE-SIMULATED with RK4; `run(id,[dur])` replays it.
//
// TO ADAPT: set m1 = m2 — the system balances and the scale reads exactly m·g.
// ============================================================================
title("Pulley scale — it reads the tension, not the weight");
canvas("16:9");
text(hdr, (cx, 46), "The scale reads the rope tension T = 2·m₁·m₂·g / (m₁+m₂)");
size(hdr, 22); color(hdr, cyan); bold(hdr); display(hdr);
pulleyscale(ps, (cx, 200), 4, 3);
text(cap, (cx, h - 32), "not (m₁+m₂)g, not the heavier weight — the tension sits between the two weights");
size(cap, 16); color(cap, dim); display(cap);
run(ps, 5);
block-tackle
A compound pulley (block & tackle): a load on a movable block held by N rope strands,
pulled by an effort mass. N strands = a mechanical advantage of N — an effort of only
load/N balances the load, but the effort end travels N× as far (blocktackle).
// ============================================================================
// block-tackle.manic — the compound pulley (mechanical advantage), Layer 1
// ----------------------------------------------------------------------------
// `blocktackle(id, [center], [load], [effort], [strands], [unit])` — a load on a
// movable block held by N rope strands and pulled by an effort mass. The N
// strands give a MECHANICAL ADVANTAGE of N: an effort of only load/N balances
// the load, and the effort end travels N× as far as the load rises. PRE-SIMULATED
// with RK4; `run(id,[dur])` replays it. N = 1 is just the Atwood machine.
//
// TO ADAPT: change `strands` (1–4) — more strands lift the same load with less
// effort, but the effort mass has to travel that much further.
// ============================================================================
title("Block & Tackle — a compound pulley's mechanical advantage");
canvas("16:9");
text(hdr, (cx, 40), "Block & tackle — 3 strands lift 8 kg with 3 kg of effort");
size(hdr, 22); color(hdr, cyan); bold(hdr); display(hdr);
blocktackle(bt, (500, 130), 8, 3, 3);
energygraph(bt, (1030, 330), 120);
text(cap, (cx, h - 32), "3 strands ⇒ MA = 3: effort just over load/3 lifts it; the effort end travels 3× as far");
size(cap, 15); color(cap, dim); display(cap);
run(bt, 5);
compound-pulley
A compound pulley with a MOVABLE pulley: a fixed top pulley carries mass A on one side
and a movable lower pulley on the other; that pulley carries B and C. The string
constraints link them (a_A = −a_P, a_B + a_C = 2·a_P); static when mA = mB+mC
(compoundpulley).
// ============================================================================
// compound-pulley.manic — fixed + movable pulley, three masses (physics L1)
// ----------------------------------------------------------------------------
// `compoundpulley(id, [center], [mA], [mB], [mC], [unit])` — a fixed top pulley
// carries mass A on one side and a MOVABLE lower pulley on the other; the movable
// pulley carries masses B and C. The string constraints link them: a_A = −a_P and
// a_B + a_C = 2·a_P (the massless movable pulley gives T₁ = 2·T₂). Static exactly
// when mA = mB + mC. PRE-SIMULATED with RK4; `run(id,[dur])` replays it.
//
// TO ADAPT: set mA = mB + mC and it balances; make A heavier and it hauls B, C up.
// ============================================================================
title("Compound pulley — fixed + movable, masses A, B, C");
canvas("16:9");
text(hdr, (cx, 40), "Compound pulley — heavier A hauls B and C upward");
size(hdr, 23); color(hdr, cyan); bold(hdr); display(hdr);
compoundpulley(cp, (470, 120), 5, 2, 2);
energygraph(cp, (1010, 330), 120);
text(cap, (cx, h - 32), "A (5 kg) > B+C (4 kg): A sinks, the movable pulley rises, B and C are pulled up together");
size(cap, 15); color(cap, dim); display(cap);
run(cp, 4);
incline-pulley
The incline-Atwood: a block on an incline tied over a pulley at the top to a hanging
mass. m₂ outpulls m₁·sinθ, so the block climbs while the mass descends — energygraph
tracks the KE↔PE trade (inclinepulley).
// incline-pulley.manic — the incline-Atwood: a block on an incline tied over a
// pulley at the top to a hanging mass. `inclinepulley(id,[center],[angle],[m1],
// [m2],[unit])`. PRE-SIMULATED (RK4); `run(id)` plays it; `energygraph` shows the
// KE↔PE trade. Matches the classic textbook figure.
title("Incline + pulley — the incline-Atwood");
canvas("16:9");
text(hdr, (cx, 44), "Block on an incline, tied over a pulley to a hanging mass"); size(hdr, 22); color(hdr, cyan); bold(hdr); display(hdr);
inclinepulley(ip, (280, 500), 30, 3, 2);
energygraph(ip, (1010, 320), 120);
text(cap, (cx, h - 32), "m₂ (2 kg) hanging outpulls m₁·sinθ, so m₁ climbs the incline and m₂ descends"); size(cap, 15); color(cap, dim); display(cap);
run(ip, 4);
double-incline
Two blocks on a wedge’s two slopes, tied over a pulley at the apex (right slope rough).
The 70 kg block on the gentle 30° slope beats the 12 kg block on the steep 50° smooth
slope — connected motion on two inclines (doubleincline).
// double-incline.manic — two blocks on a wedge's two slopes, tied over a pulley
// at the apex. `doubleincline(id,[center],[angle1],[angle2],[m1],[m2],[unit])`;
// the right slope is rough. PRE-SIMULATED (RK4); `run(id)` plays it.
title("Double incline — connected blocks on two slopes");
canvas("16:9");
text(hdr, (cx, 42), "Two slopes, one rope over the apex — which way does it slide?"); size(hdr, 22); color(hdr, cyan); bold(hdr); display(hdr);
doubleincline(di, (cx, 520), 50, 30, 12, 70);
text(cap, (cx, h - 30), "M₂ (70 kg) on the gentle rough slope beats M₁ (12 kg) on the steep smooth one"); size(cap, 15); color(cap, dim); display(cap);
run(di, 5);
incline-bumper
A block slides down an incline into a spring bumper at the base, compresses it, and
launches back up — one-sided contact, gravity PE ↔ kinetic ↔ spring PE, energy
conserved (inclinebumper).
// incline-bumper.manic — a block slides down an incline into a spring bumper at
// the base, compresses it, and launches back up (one-sided contact).
// `inclinebumper(id,[center],[angle],[mass],[stiffness],[unit])`. PRE-SIMULATED
// (RK4); `energygraph` shows gravity PE ↔ kinetic ↔ spring PE (conserved).
title("Incline + spring bumper — slide, compress, launch");
canvas("16:9");
text(hdr, (cx, 44), "A block slides down and compresses a spring at the base, then launches back"); size(hdr, 21); color(hdr, cyan); bold(hdr); display(hdr);
inclinebumper(ib, (300, 500), 40, 2, 500);
energygraph(ib, (1010, 320), 120);
text(cap, (cx, h - 32), "gravity PE → kinetic → spring PE → back: one-sided contact, energy conserved"); size(cap, 15); color(cap, dim); display(cap);
run(ib, 6);
collide-blocks
The classic momentum demo: block 1 hangs on a spring to the wall, block 2 slides in and
they collide. A live Σp readout shows momentum conserved at every collision; elastic
(e=1) keeps total energy flat while it sloshs between KE and the spring (collideblocks).
// collide-blocks.manic — the classic momentum demo. Block 1 (left) is attached to
// the wall by a SPRING; block 2 slides in freely and they collide with restitution
// e (1 = elastic → energy conserved; <1 → lost). The live Σp readout (top) shows
// momentum is conserved at every collision. Built on the shared `collide_1d`.
title("Colliding blocks — momentum is conserved");
canvas("16:9");
text(hdr, (cx, 96), "Block 1 on a spring, block 2 sliding in — watch the momentum readout"); size(hdr, 21); color(hdr, cyan); bold(hdr); display(hdr);
collideblocks(cb, (cx, 440), 3, 1, 1);
energygraph(cb, (1040, 210), 90);
text(cap, (cx, h - 36), "elastic (e=1): total energy (KE + spring PE) is conserved; Σp is conserved at each collision"); size(cap, 15); color(cap, dim); display(cap);
run(cb, 10);
collide-blocks-annotated
Conservation of momentum, the MANIC way — not a 1:1 port of the lab sim but a guided lesson: the live Σp readout as the star, the KE↔spring-PE energy view, staged callouts, and honest narration (with a wall-spring, Σp is conserved AT each collision, not constant).
// ============================================================================
// collide-blocks-annotated.manic — conservation of momentum, the MANIC way
// ----------------------------------------------------------------------------
// Not a 1:1 port of the lab sim — a guided lesson that ELEVATES it: the live Σp
// readout, the KE↔spring-PE energy view, staged callouts, and honest narration
// (with a wall-spring, Σp is conserved AT each collision — Newton's 3rd law — not
// constant, since the spring is an external force). All base manic over the sim.
// ============================================================================
title("Conservation of Momentum");
canvas("16:9");
// the sim's Σp readout sits at the very top (y≈46) — the star of the scene
collideblocks(cb, (cx, 460), 3, 1, 1);
hidden(cb); // the bare id hides every part + the readout + energy view
energygraph(cb, (1055, 250), 84); // KE ↔ spring PE (tagged cb, revealed by show(cb))
text(hdr, (cx, 104), "Conservation of Momentum"); size(hdr, 30); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
text(sub, (cx, 140), "block 1 on a spring · block 2 slides in · watch Σp through the collision"); size(sub, 17); color(sub, dim); display(sub); hidden(sub);
// callouts
text(csp, (360, 330), "spring holds block 1 to the wall"); size(csp, 16); color(csp, lime); display(csp); hidden(csp);
arrow(asp, (392, 344), (418, 424)); color(asp, dim); stroke(asp, 2); hidden(asp);
text(cb1, (470, 300), "block 1 · m₁ = 3 kg"); size(cb1, 16); color(cb1, cyan); display(cb1); hidden(cb1);
text(cb2, (800, 300), "block 2 · m₂ = 1 kg →"); size(cb2, 16); color(cb2, magenta); display(cb2); hidden(cb2);
text(cap, (cx, h - 38), ""); size(cap, 19); color(cap, fg); display(cap);
// ============================== SCRIPT ==============================
show(hdr, 0.5); show(sub, 0.4);
wait(0.3);
show(cb, 0.6); // sim + Σp readout + energy view
show(csp, 0.3); draw(asp, 0.3); show(cb1, 0.3); show(cb2, 0.3);
wait(0.4);
say(cap, "block 2 slides toward block 1, which is held by its spring", 0.4);
wait(0.5);
say(cap, "they collide — each pushes the other equal and opposite (Newton's 3rd law)", 0.4);
flash(cb.mom, gold);
wait(0.5);
say(cap, "so Σp doesn't jump at the hit — the spring only shifts it BETWEEN collisions", 0.4);
wait(0.5);
say(cap, "energy sloshes between motion and the spring, but the total stays put — elastic", 0.4);
run(cb, 12);
bullet-block
A bullet fired into a block EMBEDS (perfectly inelastic). The flight is slow-mo so you can
watch it cross, then a live speed readout collapses from 40 m/s to ~1 — momentum survives,
energy does not. Uses collide_1d(e=0) (bulletblock).
// bullet-block.manic — a bullet fired into a block EMBEDS (perfectly inelastic).
// `bulletblock(id,[center],[bulletmass],[speed],[blockmass],[unit])`. The combined
// mass crawls off at m_b·v_b/(m_b+M) — most of the kinetic energy is lost to the
// collision, so `energygraph`'s total STEPS DOWN at impact. Uses `collide_1d(e=0)`.
title("Bullet into a block — an inelastic collision");
canvas("16:9");
text(hdr, (cx, 70), "The bullet embeds: 40 m/s becomes ~1 m/s — most of the energy is gone"); size(hdr, 21); color(hdr, cyan); bold(hdr); display(hdr);
bulletblock(bb, (cx, 430), 0.05, 40, 1.95);
energygraph(bb, (1040, 200), 90);
text(cap, (cx, h - 36), "momentum is conserved, but kinetic energy is NOT — it drops sharply at impact"); size(cap, 15); color(cap, dim); display(cap);
run(bb, 6);
bullet-impact
BEST OF BOTH: the cinematic gun-shot (gun · muzzle flash · a flying cam/zoom · BOOM)
wrapped around the REAL bulletblock physics — the collision is genuinely inelastic, the
live speed readout actually collapses 40 → ~1, and the BOOM is synced to the true impact.
// ============================================================================
// bullet-impact.manic — best of both: the movie AND the physics
// ----------------------------------------------------------------------------
// The cinematic gun-shot (gun · muzzle flash · a flying camera · BOOM) wrapped
// around the REAL `bulletblock` physics sim. The collision is genuinely true —
// a perfectly inelastic impact, momentum conserved, the live speed readout
// actually collapsing 40 → ~1 — and the BOOM is SYNCED to the physical impact.
// A sim's parts are ordinary entities, so the story and the physics compose.
// ============================================================================
title("Impact — the movie meets the physics");
canvas("16:9");
// ---- the REAL physics: bullet + block, inelastic collision + live speed readout ----
// a lighter block, so the hit visibly KNOCKS it and it slides on afterward
bulletblock(bb, (cx, 430), 0.05, 40, 0.9);
hidden(bb);
sticky(bb.vel); // pin the live speed readout through the camera moves
// ---- cinematic dressing (base manic) ----
rect(barrel, (200, 380), 82, 20); color(barrel, dim); filled(barrel);
rect(body, (166, 386), 42, 34); color(body, dim); filled(body);
polygon(grip, (150, 404), (176, 404), (170, 442), (146, 438), dim);
circle(mflash, (242, 380), 28); color(mflash, gold); glow(mflash, 3.5); hidden(mflash);
text(boom, (730, 250), "BOOM!"); size(boom, 84); color(boom, magenta); bold(boom); glow(boom, 2.5); display(boom); hidden(boom);
for i in 0..14 {
let ang = i * tau / 14.0;
line(spark{i}, (730, 380), (730 + 150*cos(ang), 380 + 150*sin(ang)));
color(spark{i}, gold); stroke(spark{i}, 5); glow(spark{i}, 2); untraced(spark{i}); tag(spark{i}, sparks);
}
text(cap, (cx, h - 46), ""); color(cap, fg); size(cap, 22); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
cam((430, 400), 0.4, smooth);
say(cap, "a scene — and it's really physics underneath", 0.4);
wait(0.5);
say(cap, "FIRE!", 0.2);
par { show(mflash, 0.06); pulse(mflash); show(bb, 0.1); }
fade(mflash, 0.3);
// the bullet flies (REAL physics, slow-mo) · the camera pushes toward the block ·
// the BOOM fires exactly when the sim's bullet embeds (55% of the run) · then it
// KEEPS GOING — the block is knocked, slides on, and the camera follows it
par {
run(bb, 7);
cam((720, 380), 3.6, smooth); // push toward the impact during the flight
zoom(1.2, 3.6, smooth);
seq {
wait(3.85); // the physical impact (55% of the run)
// CONTACT → zoom-punch + BOOM
say(cap, "CONTACT!", 0.12);
par { flash(bb.block, gold); shake(bb.block, 0.6); zoom(1.6, 0.15); show(boom, 0.12); pulse(boom); draw(sparks, 0.3); }
// …and it CONTINUES — sparks clear, the block slides on, camera follows
say(cap, "…knocked back — it slides on", 0.3);
par { fade(sparks, 0.5); fade(boom, 0.7); cam((1010, 380), 2.4, smooth); zoom(1.15, 2.4, smooth); }
}
}
// it comes to rest — and the physics was true the whole way through
par { zoom(1.0, 0.9, smooth); cam((cx, 360), 0.9, smooth); }
say(cap, "…and rest. A movie on top, real physics underneath: 40 m/s → the block's crawl", 0.4);
wait(1.0);
bullet-block-annotated
The bullet’s JOURNEY, the manic way: a gun fires, a muzzle flash, a glowing bullet crosses
the gap in slow-motion and embeds — the speed readout crashing 40 → ~1. A scene, not the
bare lab sim (base-manic staging over bulletblock).
// ============================================================================
// bullet-block-annotated.manic — a bullet's journey (inelastic collision)
// ----------------------------------------------------------------------------
// Not the bare lab sim — a scene: a gun fires, a muzzle flash, a glowing bullet
// crosses the gap in slow-motion, embeds in the block, and the live speed readout
// COLLAPSES from 40 m/s to ~1. The block barely lurches — it soaked up the bullet
// but almost none of the speed. Base-manic staging over the `bulletblock` sim.
// ============================================================================
title("A bullet fired into a block");
canvas("16:9");
text(hdr, (cx, 44), "A bullet fired into a block"); size(hdr, 30); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
text(sub, (cx, 80), "perfectly inelastic: the bullet embeds — momentum survives, speed collapses"); size(sub, 17); color(sub, dim); display(sub); hidden(sub);
// the sim (bullet at the muzzle, block at rest, floor, live speed readout, energy)
bulletblock(bb, (cx, 430), 0.05, 40, 1.95);
hidden(bb);
energygraph(bb, (1055, 250), 82);
// the gun (all base-manic shapes), at the bullet's start height
rect(barrel, (200, 380), 82, 20); color(barrel, dim); filled(barrel);
rect(body, (166, 386), 42, 34); color(body, dim); filled(body);
polygon(grip, (150, 404), (176, 404), (170, 442), (146, 438), dim);
circle(flash, (242, 380), 17); color(flash, gold); glow(flash, 2.4);
hidden(barrel); hidden(body); hidden(grip); hidden(flash);
// callouts
text(cbl, (300, 336), "the bullet — light + fast"); size(cbl, 16); color(cbl, red); display(cbl); hidden(cbl);
text(cblk, (720, 300), "the block — heavy, at rest"); size(cblk, 16); color(cblk, cyan); display(cblk); hidden(cblk);
text(cap, (cx, h - 38), ""); size(cap, 19); color(cap, fg); display(cap);
// ============================== SCRIPT ==============================
show(hdr, 0.5); show(sub, 0.4);
wait(0.3);
show(bb, 0.5); show(barrel, 0.3); show(body, 0.3); show(grip, 0.3);
show(cbl, 0.3); show(cblk, 0.3);
wait(0.4);
say(cap, "a heavy block sits at rest; a fast, light bullet is loaded", 0.4);
wait(0.5);
say(cap, "FIRE — 40 m/s across the gap …", 0.4);
flash(flash, gold); pulse(flash);
run(bb, 6);
say(cap, "… it embeds. Momentum is conserved, but 40 m/s collapses to ~1 — the energy is gone", 0.4);
wait(1.0);
newtons-cradle
Newton’s cradle: pull one ball, one swings out the far side — momentum and energy pass
straight through the chain. An EVENT-DRIVEN sim (free-flight pendulums between elastic
collisions resolved by a shared 1-D impulse), the crowd-pleaser (newtonscradle).
// newtons-cradle.manic — Newton's cradle: pull N balls, N swing out. An EVENT-
// DRIVEN sim — free-flight pendulums between elastic collisions resolved by the
// shared `collide_1d` impulse. `newtonscradle(id,[center],[balls],[pulled])`.
title("Newton's cradle — momentum passes through");
canvas("16:9");
text(hdr, (cx, 70), "Pull one, one swings out — momentum and energy pass straight through"); size(hdr, 22); color(hdr, cyan); bold(hdr); display(hdr);
newtonscradle(nc, (cx, 170), 5, 1);
energygraph(nc, (1040, 470), 90);
text(cap, (cx, h - 40), "five equal balls, elastic collisions (e = 1): the chain conserves momentum and energy"); size(cap, 15); color(cap, dim); display(cap);
run(nc, 8);
string-wave
A wave on a plucked string: 36 masses on springs, both ends fixed (the discretised wave
equation). Pluck it off-centre and the pulse splits, travels, and reflects off the ends —
a rainbow chain that wiggles, pre-simulated with RK4 (stringwave).
// string-wave.manic — a wave on a plucked string: N masses on springs, both ends
// fixed (the discretised wave equation). `stringwave(id,[center],[width],[amp],
// [pluck])`. Pluck it off-centre and the pulse splits into two, travels out, and
// reflects (inverting) off the fixed ends. Drawn as a rainbow chain that wiggles.
title("Wave on a string — pluck it and watch it travel");
canvas("16:9");
text(hdr, (cx, 70), "Pluck a string: the pulse splits, travels, and reflects off the ends"); size(hdr, 23); color(hdr, cyan); bold(hdr); display(hdr);
stringwave(sw, (cx, 380), 900, 110, 0.28);
text(cap, (cx, h - 40), "36 masses on springs, fixed at both ends — the wave equation, pre-simulated with RK4"); size(cap, 16); color(cap, dim); display(cap);
run(sw, 10);
loop-track
A ball rolls down a ramp and around a vertical LOOP-THE-LOOP — the curved-track case.
A bead energy solver (v = √(2g(H−y)) along the arc) so it visibly slows at the top;
release above 2·radius to clear it. energygraph tracks KE↔PE (looptrack).
// loop-track.manic — a ball rolls down a ramp and around a vertical loop-the-loop.
// `looptrack(id,[center],[radius],[height],[unit])`. A curved-track energy solver:
// v = √(2g(H−y)) along the arc, so the ball visibly SLOWS at the top. The release
// height must exceed 2·radius to clear the top. `energygraph` shows KE↔PE.
title("Loop-the-loop — down the ramp, around the loop");
canvas("16:9");
text(hdr, (cx, 44), "Release high enough (H > 2r) and the ball clears the top of the loop"); size(hdr, 21); color(hdr, cyan); bold(hdr); display(hdr);
looptrack(lt, (470, 560), 1, 3);
energygraph(lt, (1010, 330), 120);
text(cap, (cx, h - 32), "energy conserved: it trades kinetic for potential, slowest at the top of the loop"); size(cap, 15); color(cap, dim); display(cap);
run(lt, 5);
loop-cinematic
The loop-the-loop as a MOVIE with real physics inside: the camera pushes in as the ball
climbs, and the tension is genuine — a modest release height means it truly crawls over
the top before rocketing out. cam/zoom beats synced to the looptrack sim.
// ============================================================================
// loop-cinematic.manic — the loop-the-loop as a movie (real physics inside)
// ----------------------------------------------------------------------------
// Same recipe as bullet-impact: build the real `looptrack` sim, aim the camera,
// sync the beats to the physics. A ball is released, races down the ramp, and
// the camera pushes in as it climbs the loop — the tension is REAL (it genuinely
// slows at the top, energy traded for height), then it clears and rockets out.
// ============================================================================
title("Loop the Loop");
canvas("16:9");
// the real curved-track physics — a modest release height, so it BARELY clears
// the top (H just over 2·r) — that's where the real tension lives
looptrack(lt, (500, 560), 1, 2.4, 95);
hidden(lt);
text(cap, (cx, h - 46), ""); color(cap, fg); size(cap, 24); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
cam((420, 400), 0.4, smooth); // frame the whole ramp + loop
say(cap, "release…", 0.4);
show(lt, 0.5);
wait(0.4);
// down the ramp, into the loop — the camera pushes in for the climb
par {
run(lt, 6.0);
seq {
say(cap, "released — down the ramp!", 0.3);
par { cam((500, 455), 3.0, smooth); zoom(1.35, 3.0, smooth); } // slow push through the descent + entry
say(cap, "up and over — will it clear the top?!", 0.3);
wait(1.7); // the climb + the crawl over the top
say(cap, "…MADE IT!", 0.25);
par { cam((470, 410), 1.3, smooth); zoom(1.1, 1.3, smooth); } // ease back as it rockets out
}
}
// the physics was the whole story
say(cap, "…real energy: fastest at the bottom, barely crawling over the top", 0.4);
wait(1.0);
spring-chain
Three blocks joined by two springs on an incline — coupled oscillators. Pull one and the
whole chain rings (normal modes / beating); shown in the incline’s frame since uniform
gravity doesn’t touch the internal motion (springchain).
// spring-chain.manic — three blocks joined by two springs on an incline: coupled
// oscillators / normal modes. `springchain(id,[center],[angle],[unit])`. A uniform
// gravity component doesn't change the internal motion, so it's shown in the
// incline's frame (CM held). `energygraph` shows the energy sloshing (beating).
title("Spring chain on an incline — coupled oscillators");
canvas("16:9");
text(hdr, (cx, 44), "Three masses, two springs — the energy sloshs between the modes"); size(hdr, 21); color(hdr, cyan); bold(hdr); display(hdr);
springchain(sc, (500, 300), 22);
energygraph(sc, (1010, 340), 120);
text(cap, (cx, h - 32), "pull one block and the whole chain rings — normal modes / beating (coupled springs)"); size(cap, 15); color(cap, dim); display(cap);
run(sc, 9);
incline-showcase
One paper page, FOUR live incline problems: a friction ramp, an incline+pulley, a
two-slope wedge, and a spring bumper — revealed one at a time with narration, then all
run in parallel. Real base-manic staging (template("paper") + hidden/show + say
par), not a physics dump.
// ============================================================================
// incline-showcase.manic — four incline problems, one paper page, all live
// ----------------------------------------------------------------------------
// A single annotated lesson that puts ALL FOUR animated incline sims on one
// `template("paper")` page and runs them together — real base-manic composition
// (staged reveal with `hidden`/`show`, `say` narration, per-quadrant labels),
// not a physics dump. Each sim's bare id broadcasts, so `hidden(rp)`/`show(rp)`
// address a whole sim at once. Finishes with all four running in parallel.
// ============================================================================
title("Four inclined-plane problems — one page");
canvas("16:9");
template("paper");
text(hdr, (cx, 34), "Four inclined-plane problems, one page"); color(hdr, fg); size(hdr, 26); bold(hdr); display(hdr); hidden(hdr);
// the four sims (each hidden; the bare id broadcasts to all its parts)
ramp(rp, (110, 340), 28, 5, 0, 45); hidden(rp);
inclinepulley(ip, (690, 340), 30, 3, 2, 42); hidden(ip);
doubleincline(dw, (300, 660), 50, 30, 12, 70, 34); hidden(dw);
inclinebumper(ib, (720, 660), 38, 2, 500, 42); hidden(ib);
// quadrant labels
text(l1, (150, 175), "① friction on a ramp"); color(l1, dim); size(l1, 17); display(l1); hidden(l1);
text(l2, (770, 175), "② incline + pulley"); color(l2, dim); size(l2, 17); display(l2); hidden(l2);
text(l3, (150, 505), "③ two-slope wedge"); color(l3, dim); size(l3, 17); display(l3); hidden(l3);
text(l4, (770, 505), "④ spring bumper"); color(l4, dim); size(l4, 17); display(l4); hidden(l4);
text(cap, (cx, h - 26), ""); color(cap, fg); size(cap, 19); display(cap);
// ============================== SCRIPT ==============================
show(hdr, 0.5);
say(cap, "one page, four classic incline problems — all real, pre-simulated physics", 0.4);
wait(0.5);
say(cap, "① a block sliding down a rough ramp", 0.4);
show(rp, 0.5); show(l1, 0.3);
wait(0.5);
say(cap, "② a block on an incline, tied over a pulley to a hanging mass", 0.4);
show(ip, 0.5); show(l2, 0.3);
wait(0.5);
say(cap, "③ two blocks on a wedge, connected over the apex", 0.4);
show(dw, 0.5); show(l3, 0.3);
wait(0.5);
say(cap, "④ a block sliding into a spring bumper at the base", 0.4);
show(ib, 0.5); show(l4, 0.3);
wait(0.6);
say(cap, "…and they all run — same page, same physics engine, four different motions", 0.4);
par {
run(rp, 7);
run(ip, 7);
run(dw, 7);
run(ib, 7);
}
textbook-incline-fbd
A block on an incline as a physics-class FREE-BODY DIAGRAM: the reusable forces(id)
view draws gravity/normal/friction/a vectors on the block, a second panel redraws them
from a point, and template("paper") inks it — then run slides the block (ramp).
// ============================================================================
// textbook-incline-fbd.manic — the free-body diagram, textbook style
// ----------------------------------------------------------------------------
// A block on an incline shown the classic physics-class way: the physical
// picture on the left with the force vectors ON the block (via the reusable
// `forces(id)` view on the `ramp` sim — gravity `mg`, normal `N`, friction `f`,
// and the acceleration `a`), and a free-body-diagram panel on the right drawing
// the same forces from a single point. `template("paper")` inks it; then `run`
// plays the slide with the vectors riding the block.
// ============================================================================
title("Block on an incline — the free-body diagram");
canvas("16:9");
template("paper");
text(hdr, (cx, 42), "Block on an incline — the free-body diagram"); color(hdr, fg); size(hdr, 24); bold(hdr); display(hdr);
// left: the incline sim, inked; its force vectors revealed by forces()
ramp(rp, (170, 470), 30);
outlined(rp.block); outline(rp.block, fg);
// right: the free-body diagram — the same four forces from one point
dot(o, (1000, 330), 6); color(o, fg);
text(fbdT, (1000, 205), "free-body diagram"); color(fbdT, dim); size(fbdT, 16); display(fbdT);
arrow(aN, (1000, 330), (955, 252)); color(aN, lime); stroke(aN, 3);
text(lN, (944, 240), "N"); color(lN, lime); size(lN, 18); display(lN);
arrow(af, (1000, 330), (1078, 285)); color(af, magenta); stroke(af, 3);
text(lf, (1086, 280), "f"); color(lf, magenta); size(lf, 18); display(lf);
arrow(ag, (1000, 330), (1000, 440)); color(ag, blue); stroke(ag, 3);
text(lg, (1010, 448), "mg"); color(lg, blue); size(lg, 18); display(lg);
arrow(aa, (1000, 330), (930, 372)); color(aa, red); stroke(aa, 3);
text(la, (908, 380), "a"); color(la, red); size(la, 18); display(la);
text(cap, (cx, h - 34), ""); color(cap, fg); size(cap, 19); display(cap);
// ============================== SCRIPT ==============================
say(cap, "three forces act on the block: gravity, the normal force, and friction", 0.4);
forces(rp, 0.9);
wait(1.0);
say(cap, "drawn from a single point, they make the free-body diagram →", 0.4);
wait(0.9);
say(cap, "their sum points down the slope, so the block accelerates and slides", 0.4);
run(rp, 5);
pulley-annotated
The Atwood machine elevated with CAMERA work: cam + zoom push in on the two masses
for the imbalance beat and glow the heavier one, a counter ticks the acceleration up,
then it pulls back to release — cinematography instead of section cards.
// ============================================================================
// pulley-annotated.manic — ELEVATE a sim: CAMERA work (cam + zoom)
// ----------------------------------------------------------------------------
// A different elevation flavour from spring-annotated (typewriter) — here the
// camera does the storytelling: `cam` + `zoom` push in on the two masses for
// the "imbalance" beat, glow the heavier one, then pull back for the release.
// A live `counter` ticks the acceleration up. No stage-covering section cards,
// so the motion is always visible. All base manic over the `pulley` parts.
// ============================================================================
title("The Atwood Machine — Why the Heavier Side Falls");
canvas("16:9");
text(hdr, (cx, 40), "The Atwood Machine"); size(hdr, 28); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
// ---- the sim, centred so the camera can push in ----
pulley(pl, (cx, 190), 3, 2);
hidden(pl.wheel); hidden(pl.hub); hidden(pl.ropeL); hidden(pl.ropeR); hidden(pl.mass1); hidden(pl.mass2);
text(wheelL, (cx, 118), "frictionless pulley"); size(wheelL, 17); color(wheelL, dim); display(wheelL); hidden(wheelL);
text(m1L, (cx - 150, 320), "m₁ = 3 kg"); size(m1L, 19); color(m1L, cyan); display(m1L); hidden(m1L);
text(m2L, (cx + 150, 320), "m₂ = 2 kg"); size(m2L, 19); color(m2L, magenta); display(m2L); hidden(m2L);
counter(aC, (cx, 250), 0, 2, "a = ", " m/s²"); size(aC, 26); color(aC, gold); display(aC); hidden(aC); sticky(aC);
// caption is `sticky` — it stays pinned to the screen through the camera push-in
text(cap, (cx, h - 44), ""); size(cap, 21); color(cap, dim); display(cap); sticky(cap);
// ============================== SCRIPT ==============================
show(hdr, 0.5);
say(cap, "one rope over one frictionless pulley …", 0.4);
show(pl.wheel, 0.4); show(pl.hub, 0.3); pulse(pl.wheel); show(wheelL, 0.4);
wait(0.3);
say(cap, "… a heavier mass on the left, a lighter one on the right", 0.4);
show(pl.ropeL, 0.3); show(pl.mass1, 0.4); pulse(pl.mass1); show(m1L, 0.4);
show(pl.ropeR, 0.3); show(pl.mass2, 0.4); pulse(pl.mass2); show(m2L, 0.4);
wait(0.5);
// ---- CAMERA: push in on the masses for the imbalance beat ----
// cap + aC are sticky, so they stay put on screen while the world zooms
say(cap, "look closely — the net pull favours the heavier side", 0.4);
par { cam((cx, 330), 1.4, smooth); zoom(1.7, 1.4, smooth); }
glow(pl.mass1, 1.9); flash(pl.mass1, gold); pulse(pl.mass1);
wait(1.0);
par { cam((cx, cy), 1.2, smooth); zoom(1, 1.2, smooth); } // pull back out
wait(0.2);
// the acceleration, as a live readout
show(aC, 0.3); to(aC, value, 1.96, 1.0); // a = (m₁−m₂)g/(m₁+m₂)
fade(wheelL, 0.3); fade(m1L, 0.3); fade(m2L, 0.3);
wait(0.5);
say(cap, "release: m₁ sinks, m₂ rises", 0.4);
run(pl, 4);
pulley-paper
The SAME Atwood sim dressed as a textbook figure AND run: template("paper") inks it
automatically, a hatched support ceiling, a forest-green wheel and outlined mass boxes,
a base-manic reveal, then run plays the motion — the paper treatment on a LIVE sim.
// ============================================================================
// pulley-paper.manic — an ANIMATED sim in textbook paper style
// ----------------------------------------------------------------------------
// The Atwood `pulley` sim, dressed as a physics-textbook figure AND run: the
// `template("paper")` page (its palette remap inks the sim automatically), a
// hatched `support` ceiling, a forest-green wheel, outlined mass boxes, and a
// base-manic reveal (`say`/`show`/`draw`/`fade`) — then `run` plays the motion.
// Proof the paper/support treatment composes with a live, animated simulation.
// ============================================================================
title("The Atwood machine — textbook style");
canvas("16:9");
template("paper"); // white page; the theme remaps neon → ink
text(hdr, (cx, 46), "The Atwood machine"); color(hdr, fg); size(hdr, 27); bold(hdr); display(hdr); hidden(hdr);
// hatched ceiling + suspension rope (traced on)
support(ceil, (cx, 108), 320); untraced(ceil);
line(sus, (cx, 108), (cx, 160)); color(sus, fg); stroke(sus, 2); untraced(sus);
// the sim, restyled to textbook ink (parts hidden, revealed in order)
pulley(pl, (cx, 192), 3, 2);
color(pl.wheel, lime); // lime → forest green on the paper palette
color(pl.hub, fg); color(pl.ropeL, fg); color(pl.ropeR, fg);
outlined(pl.mass1); outline(pl.mass1, fg);
outlined(pl.mass2); outline(pl.mass2, fg);
hidden(pl.wheel); hidden(pl.hub); hidden(pl.ropeL); hidden(pl.ropeR); hidden(pl.mass1); hidden(pl.mass2);
// annotations
text(m1L, (cx - 150, 316), "m₁ = 3 kg"); color(m1L, fg); size(m1L, 20); display(m1L); hidden(m1L);
text(m2L, (cx + 150, 316), "m₂ = 2 kg"); color(m2L, fg); size(m2L, 20); display(m2L); hidden(m2L);
text(acc, (cx, 604), "a = (m₁ − m₂)·g / (m₁ + m₂)"); color(acc, fg); size(acc, 22); display(acc); hidden(acc);
text(cap, (cx, h - 40), ""); color(cap, fg); size(cap, 20); display(cap);
// ============================== SCRIPT ==============================
show(hdr, 0.5);
say(cap, "a rope over one frictionless pulley, hung from the ceiling", 0.4);
draw(ceil, 0.4); draw(sus, 0.4); show(pl.wheel, 0.4); show(pl.hub, 0.3);
wait(0.4);
say(cap, "a heavier mass on the left, a lighter one on the right", 0.4);
show(pl.ropeL, 0.3); show(pl.mass1, 0.4); show(m1L, 0.3);
show(pl.ropeR, 0.3); show(pl.mass2, 0.4); show(m2L, 0.3);
wait(0.5);
say(cap, "the system accelerates toward the heavier side", 0.4);
show(acc, 0.5);
wait(0.7);
fade(m1L, 0.3); fade(m2L, 0.3);
say(cap, "release: m₁ sinks, m₂ rises", 0.4);
run(pl, 4);
ramp
A block sliding down an inclined plane with static/kinetic friction — the full force
model. Friction turns motion into heat, so the total-energy line steadily falls
(ramp + energygraph).
// ============================================================================
// ramp.manic — friction on an incline, on the physics baseline (Layer 1)
// ----------------------------------------------------------------------------
// `ramp(id, [center], [angle], [mass], [applied], [unit])` — a block sliding
// down an inclined plane. Full force model: gravity along the slope, the normal
// force, and a static→kinetic friction switch. Friction turns mechanical energy
// into heat, so `energygraph`'s total DECAYS as the block slides. PRE-SIMULATED
// with RK4; `run(id,[dur])` replays it.
//
// TO ADAPT: lower the angle below the friction angle and the block won't budge.
// ============================================================================
title("Inclined plane — friction dissipates energy");
canvas("16:9");
text(hdr, (cx, 44), "Block on a ramp — μ friction bleeds the energy away");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
ramp(rp, (300, 470), 30);
energygraph(rp, (960, 320), 120);
text(cap, (cx, h - 32), "as the block slides down, the gold total energy line falls — friction converts it to heat");
size(cap, 16); color(cap, dim); display(cap);
run(rp, 6);
drop-mass
A mass dropped onto a spring-block STICKS — a perfectly inelastic collision. Watch the
total-energy line step down at impact, then the heavier combined mass oscillate about
a lower equilibrium (dropmass + energygraph).
// ============================================================================
// drop-mass.manic — an inelastic collision, on the physics baseline (Layer 1)
// ----------------------------------------------------------------------------
// `dropmass(id, [center], [dropheight], [unit])` — a mass is dropped onto a
// block resting on a spring. It free-falls, STICKS (perfectly inelastic), then
// the heavier combined mass oscillates about a lower equilibrium. The collision
// loses kinetic energy — so `energygraph`'s total STEPS DOWN at impact. Then it
// is conserved again. PRE-SIMULATED with RK4; `run(id,[dur])` replays it.
//
// TO ADAPT: raise dropheight for a faster impact and a bigger energy step.
// ============================================================================
title("Drop mass — an inelastic collision loses energy");
canvas("16:9");
text(hdr, (cx, 44), "Dropped mass sticks — energy is lost in the inelastic collision");
size(hdr, 22); color(hdr, cyan); bold(hdr); display(hdr);
dropmass(dm, (440, 150), 1.2);
energygraph(dm, (960, 330), 120);
text(cap, (cx, h - 32), "watch the gold total energy line step DOWN the instant the magenta mass sticks to the block");
size(cap, 15); color(cap, dim); display(cap);
run(dm, 7);
raft-cm
A person walks back and forth on a floating raft; with no external force the centre of
mass stays fixed, so the raft glides the opposite way — momentum conservation you can
see (raft).
// ============================================================================
// raft-cm.manic — centre of mass, on the physics baseline (Layer 1)
// ----------------------------------------------------------------------------
// `raft(id, [center], [personmass], [raftmass], [unit])` — a person walking on
// a floating raft. With no external horizontal force, momentum is conserved and
// the centre of mass stays FIXED: the raft slides the opposite way by
// −m_person/(m_person+m_raft) of each step. PRE-SIMULATED; `run(id,[dur])`
// replays it. The dashed line marks the (unmoving) centre of mass.
//
// TO ADAPT: make the raft much lighter (raftmass) — it slides much further.
// ============================================================================
title("Raft — walk one way, the raft slides the other");
canvas("16:9");
text(hdr, (cx, 46), "Momentum stays zero, so the centre of mass never moves");
size(hdr, 23); color(hdr, cyan); bold(hdr); display(hdr);
raft(rf, (cx, 380), 70, 200);
text(cap, (cx, h - 32), "the person walks; the raft glides back — the dashed centre-of-mass line stays put");
size(cap, 16); color(cap, dim); display(cap);
run(rf, 9);
brachistochrone
Four beads race under gravity from A to B down a straight line, a circular arc, a
parabola, and a cycloid. The cycloid — the curve of fastest descent — wins, even
though it dips lower and travels farther (brachistochrone).
// ============================================================================
// brachistochrone.manic — the curve of fastest descent (physics Layer 1)
// ----------------------------------------------------------------------------
// `brachistochrone(id, [center], [unit])` — four beads race under gravity from
// A down to B along four curves: a straight line, a circular arc, a parabola,
// and a CYCLOID. Each is a full RK4 bead-on-wire integration. The cycloid — the
// brachistochrone — wins, even though it dips below and travels farther. The
// steep early drop buys speed that more than pays back the extra distance.
// `run(id,[dur])` replays the race.
//
// TO ADAPT: raise `dur` to watch the finish order in slow motion.
// ============================================================================
title("Brachistochrone — the fastest slide is a cycloid");
canvas("16:9");
text(hdr, (cx, 44), "Which curve is fastest from A to B? Steepness early beats a short path");
size(hdr, 21); color(hdr, cyan); bold(hdr); display(hdr);
brachistochrone(br, (330, 150), 150);
text(leg, (cx, h - 58), "dim: straight cyan: arc gold: parabola magenta: CYCLOID (winner)");
size(leg, 16); color(leg, fg); display(leg);
text(cap, (cx, h - 30), "same start, same finish — the magenta cycloid bead reaches B first");
size(cap, 16); color(cap, dim); display(cap);
run(br, 5);
brachistochrone-annotated
The elevation recipe on a RACE, told with KINETIC TYPOGRAPHY: wordpop pops the
question in, karaoke sweeps a highlight across the four path names as the curves
sketch on, then flash/glow crown the cycloid — a third, distinct elevation style.
// ============================================================================
// brachistochrone-annotated.manic — ELEVATE a sim: KINETIC TYPOGRAPHY
// ----------------------------------------------------------------------------
// A third elevation flavour (see spring-annotated for typewriter, pulley-
// annotated for camera). Here the text itself performs: `caption` + `wordpop`
// pops the question in word-by-word, `caption` + `karaoke` sweeps a highlight
// across the four path names as the curves are sketched, and `flash`/`glow`/
// `pulse` crown the winner — the same recipe on a multi-body RACE, no section
// cards. All base manic over the `brachistochrone` parts.
// ============================================================================
title("Brachistochrone — Shortest Path, or Fastest?");
canvas("16:9");
// ---- the sim (curves untraced to sketch on; beads + markers hidden) ----
brachistochrone(br, (330, 165), 150);
untraced(br.straight); untraced(br.circle); untraced(br.parabola); untraced(br.cycloid);
hidden(br.bead_straight); hidden(br.bead_circle); hidden(br.bead_parabola); hidden(br.bead_cycloid);
hidden(br.markA); hidden(br.labelA); hidden(br.markB); hidden(br.labelB);
// ---- kinetic-typography captions ----
caption(q, "shortest path — or fastest?", (cx, 62), 34, gold);
caption(leg, "straight arc parabola cycloid", (cx, h - 66), 26, dim);
text(cap, (cx, h - 32), ""); size(cap, 19); color(cap, dim); display(cap);
// ============================== SCRIPT ==============================
say(cap, "a bead slides from A down to B under gravity — same start, same finish", 0.4);
show(br.markA, 0.4); show(br.labelA, 0.3); pulse(br.markA);
show(br.markB, 0.4); show(br.labelB, 0.3); pulse(br.markB);
wait(0.4);
// the question pops in, word by word
wordpop(q, 0.18);
wait(0.6);
// sketch the four curves; the legend highlight sweeps across their names
say(cap, "four paths from A to B — the cycloid dips below the line", 0.4);
draw(br.straight, 0.5); draw(br.circle, 0.5); draw(br.parabola, 0.5); draw(br.cycloid, 0.6);
karaoke(leg, 0.5, cyan);
flash(br.cycloid, magenta);
wait(0.5);
// the race
say(cap, "release all four at once …", 0.4);
show(br.bead_straight, 0.3); show(br.bead_circle, 0.3); show(br.bead_parabola, 0.3); show(br.bead_cycloid, 0.3);
run(br, 6);
// crown the winner
say(cap, "the CYCLOID wins — the steep early drop buys speed that repays the longer path", 0.4);
flash(br.cycloid, lime); glow(br.bead_cycloid, 1.9); pulse(br.bead_cycloid);
wait(1.0);
textbook-pulley
A physics-TEXTBOOK figure, manic style: the template("paper") white page, a hatched
support ceiling, a green pulley wheel, and outlined labelled mass boxes — the classic
m over 2m+3m arrangement, all base primitives.
// ============================================================================
// textbook-pulley.manic — a physics-textbook figure, manic style
// ----------------------------------------------------------------------------
// The classic "m over 2m+3m" pulley arrangement, drawn in the flat textbook
// look: the `paper` theme (white page, dark ink), a hatched `support` ceiling,
// a solid green pulley wheel, thin ropes, and outlined labelled mass boxes.
// A STATIC figure — pure base manic (`support`, `circle`, `line`, `rect`, `text`).
// ============================================================================
title("Pulley arrangement — m, 2m, 3m");
canvas("16:9");
template("paper");
// hatched ceiling + the rope suspending the pulley
support(ceil, (cx, 120), 340);
line(rope0, (cx, 120), (cx, 222)); color(rope0, fg); stroke(rope0, 2);
// the green pulley wheel (dark edge + hub)
circle(wheel, (cx, 300), 80); color(wheel, lime); outline(wheel, fg);
circle(hub, (cx, 300), 5); color(hub, fg); filled(hub);
// ropes leave the wheel vertically, left and right
line(ropeL, (560, 300), (560, 456)); color(ropeL, fg); stroke(ropeL, 2);
line(ropeR, (720, 300), (720, 456)); color(ropeR, fg); stroke(ropeR, 2);
// mass on the left: m
rect(mbox, (560, 485), 110, 58); outlined(mbox); outline(mbox, fg); stroke(mbox, 2);
text(ml, (560, 485), "m"); color(ml, fg); size(ml, 26); bold(ml);
// masses on the right: 2m, with 3m hanging below it
rect(m2, (720, 485), 120, 58); outlined(m2); outline(m2, fg); stroke(m2, 2);
text(l2, (720, 485), "2m"); color(l2, fg); size(l2, 26); bold(l2);
line(r23, (720, 514), (720, 596)); color(r23, fg); stroke(r23, 2);
rect(m3, (720, 625), 120, 58); outlined(m3); outline(m3, fg); stroke(m3, 2);
text(l3, (720, 625), "3m"); color(l3, fg); size(l3, 26); bold(l3);
textbook-tension
Another textbook figure: two support ropes at 60°/30° meeting a knot, a string over a
hanging pulley carrying 10 kg with the other end anchored to a hatched floor — support
template("paper")for the flat exam-paper look.
// ============================================================================
// textbook-tension.manic — a physics-textbook figure, manic style
// ----------------------------------------------------------------------------
// Two support ropes at 60° / 30° meet at a knot; a string over a hanging pulley
// carries a 10 kg load on one side and anchors to the floor on the other. Drawn
// in the flat textbook look: `paper` theme, hatched `support` ceiling + floor,
// a green pulley wheel, and outlined labels. A STATIC figure, all base manic.
// ============================================================================
title("Tension in a rope over a pulley (g = 10 m/s²)");
canvas("16:9");
template("paper");
// hatched ceiling
support(ceil, (cx, 110), 700);
// the two support ropes meeting at the knot, with a dashed-style vertical ref
line(t1, (553, 110), (cx, 260)); color(t1, fg); stroke(t1, 2);
line(t2, (900, 110), (cx, 260)); color(t2, fg); stroke(t2, 2);
line(vref, (cx, 110), (cx, 260)); color(vref, dim); stroke(vref, 1);
text(a1, (588, 150), "60°"); color(a1, fg); size(a1, 22);
text(a2, (852, 150), "30°"); color(a2, fg); size(a2, 22);
text(t1l, (556, 210), "T₁"); color(t1l, fg); size(t1l, 24); bold(t1l);
text(t2l, (820, 210), "T₂"); color(t2l, fg); size(t2l, 24); bold(t2l);
// knot → hanging pulley
line(drop, (cx, 260), (cx, 364)); color(drop, fg); stroke(drop, 2);
circle(wheel, (cx, 430), 66); color(wheel, lime); outline(wheel, fg);
circle(hub, (cx, 430), 5); color(hub, fg); filled(hub);
// 10 kg load on the right side of the string
line(ropeM, (706, 430), (706, 520)); color(ropeM, fg); stroke(ropeM, 2);
rect(mbox, (706, 548), 84, 56); outlined(mbox); outline(mbox, fg); stroke(mbox, 2);
text(ml, (792, 548), "10 Kg"); color(ml, fg); size(ml, 22); bold(ml);
// the other side anchored to a hatched floor
line(ropeF, (cx, 496), (cx, 600)); color(ropeF, fg); stroke(ropeF, 2);
support(floor, (cx, 612), 150, "up");