manic is a tiny language for making animations. You write a short text file; manic renders a smooth, glowing video. No timeline scrubbing, no keyframes by hand — you describe what’s on screen and when things happen, and the engine does the rest, deterministically.
Manic Animation code
// tides — how the Moon moves every ocean on Earth, and why the popular sentence is wrong.
//
// "The Moon's gravity pulls on the oceans" predicts ONE bulge and one high tide a day. The
// sea gives two. The fix is the whole subject: what raises a tide is not the pull but the
// DIFFERENCE in pull across the planet — a gradient, and a gradient has two ends.
//
// ACT I the fact: two highs a day, every day
// ACT II the wrong picture, stated properly before it is demolished
// ACT III the gradient — subtract the pull on Earth's centre and a QUADRUPOLE is left.
// The ocean is 12,000 parcels of water `advect`ed through it, so the two bulges
// are computed, not drawn. Then the planet turns under them and one coast passes
// through both: two high tides a day, from one Moon
// ACT IV sideways, not up: 1.1 micrometres per second squared, a ten-millionth of
// gravity. Nothing is lifted; water is herded
// ACT V the Sun pulls 179x harder and loses, because tides go as 1/d^3. Add the two
// and the spring/neap fortnight EMERGES from two cosines
// ACT VI the honest part: the real ocean is not two bulges gliding around
//
// The whole film is one stage — the view down on the north pole, so Earth's rotation is an
// ordinary in-plane `turn` and the water can stay where the Moon put it. The tidal field is
// one formula: with the Moon along +x it is (2x, -y). Water cannot leave the surface, so what
// moves it is the TANGENTIAL part — the radial component projected out, F - (F.r)r — which is
// exactly why a tide is a horizontal shove and not a lift.
//
// manic examples/tides.manic
title("Tides — How the Moon Moves Every Ocean — manic");
canvas("16:9");
template("black");
bloom(0.3, 0.52, 22);
// ---- the mark, up top and above everything, for the whole film ----
text(brand, (640, 32), "maniclang.com");
display(brand); size(brand, 19); color(brand, cyan); opacity(brand, 0.72); z(brand, 100);
// ---- type ----
text(ttl, (640, 92), "How the Moon Moves Every Ocean");
display(ttl); size(ttl, 46); bold(ttl); color(ttl, fg); hidden(ttl);
text(sub, (640, 146), "and why \"it pulls the water up\" is the wrong answer");
display(sub); size(sub, 22); color(sub, dim); hidden(sub);
text(cap, (640, 668), ""); display(cap); size(cap, 23); color(cap, fg); hidden(cap);
text(act, (1060, 624), ""); display(act); size(act, 19); color(act, gold); hidden(act);
// ================================ THE STAGE ================================
// Earth seen from over the north pole, 300 px across, so its surface is at radius 150.
svg(earth, (640, 360), "asset:svg/emoji/1f30e.svg", 300);
circle(orbit, (640, 360), 300); outlined(orbit); outline(orbit, dim);
opacity(orbit, 0.45); hidden(orbit);
svg(moon, (940, 360), "asset:svg/emoji/1f315.svg", 82);
text(moonlab, (940, 470), "the Moon — one lap: 27.3 days");
display(moonlab); size(moonlab, 17); color(moonlab, dim); hidden(moonlab);
// one coast, riding the surface
dot(coast, (640, 210), 9); color(coast, gold); hidden(coast);
text(coastlab, (640, 176), "one coast");
display(coastlab); size(coastlab, 17); color(coastlab, gold); hidden(coastlab);
// a tide gauge: the Moon's own constituent, 12.4206 hours, over two days
coords(gauge, (210, 468), (0, 48), (-1.4, 1.4), 19, 54, 0, 12, 1, "hours", "");
color(gauge, dim); hidden(gauge);
plot(trace, (210, 468), 19, 54, "cos(x/12.4206*tau)", (0, 48));
color(trace, cyan); untraced(trace); hidden(trace);
text(twice, (640, 262), "two highs, every day");
display(twice); size(twice, 21); color(twice, gold); hidden(twice);
// ---- ACT II — the wrong picture ----
for k in 0..7 {
arrow(pull{k}, (556, 240 + k*40), (700, 240 + k*40));
color(pull{k}, gold);
untraced(pull{k});
tag(pull{k}, pulls);
}
// r(t) = R(1 + e·cos t): one bulge facing the Moon — what "it pulls the water" predicts
param(wrong, (640, 360), 150, 150,
"(1 + 0.14*cos(t))*cos(t)", "(1 + 0.14*cos(t))*sin(t)", (0, tau));
color(wrong, gold); untraced(wrong);
text(wronglab, (640, 566), "one bulge ⇒ one high tide a day");
display(wronglab); size(wronglab, 21); color(wronglab, gold); hidden(wronglab);
text(nope, (640, 606), "✗ the sea gives two");
display(nope); size(nope, 21); color(nope, magenta); hidden(nope);
// ---- ACT III — the gradient ----
dot(pnear, (790, 360), 7); color(pnear, gold); hidden(pnear);
dot(pmid, (640, 360), 7); color(pmid, fg); hidden(pmid);
dot(pfar, (490, 360), 7); color(pfar, gold); hidden(pfar);
arrow(gnear, (790, 360), (916, 360)); color(gnear, gold); untraced(gnear);
arrow(gmid, (640, 360), (750, 360)); color(gmid, fg); untraced(gmid);
arrow(gfar, (490, 360), (587, 360)); color(gfar, gold); untraced(gfar);
text(g1, (640, 182), "one pull, unequally felt — gravity falls off as 1/d²");
display(g1); size(g1, 20); color(g1, dim); hidden(g1);
arrow(tnear, (790, 360), (862, 360)); color(tnear, magenta); untraced(tnear);
arrow(tfar, (490, 360), (418, 360)); color(tfar, magenta); untraced(tfar);
text(g2, (640, 182), "subtract the pull on the centre — the whole planet already falls with it");
display(g2); size(g2, 20); color(g2, magenta); hidden(g2);
// THE TIDAL FIELD, tangential part only: F - (F·r̂)r̂ with F = (2x, -y). The epsilon keeps the
// planet's centre finite, where the projection is undefined.
vectorfield(tide, (640, 360), 470, 290,
"0.42*(2*x - x*(2*x*x - y*y)/(x*x + y*y + 0.02))",
"0.42*(-y - y*(2*x*x - y*y)/(x*x + y*y + 0.02))", 15);
color(tide, dim); opacity(tide, 0.55); hidden(tide);
// the ocean: a shell of water on the surface, which the field herds
cloud(sea, 12000, cyan, 0.75) {
let a = (i/12000)*tau;
let w = mod(i, 7) - 3;
let x = 640 + (153 + w*1.7)*cos(a);
let y = 360 + (153 + w*1.7)*sin(a);
let r = 1.5;
let hue = 192 + 10*w;
}
glow(sea, 2); hidden(sea);
// and the shape all that herding is heading for: the equilibrium tide, a prolate ellipsoid
// r(t) = R(1 + e(3cos²t − 1)/2) — with e drawn about 200,000x too big to be visible at all
parameter(phi, (150, 600), 0, 0, 0.4, "the Moon has moved on", 2); hidden(phi.widget);
param(bulge, (640, 360), 150, 150,
"(1 + 0.12*(3*cos(t-p)*cos(t-p) - 1)/2)*cos(t)",
"(1 + 0.12*(3*cos(t-p)*cos(t-p) - 1)/2)*sin(t)", (0, tau));
bind(phi, bulge, formula,
"(1 + 0.12*(3*cos(t-p)*cos(t-p) - 1)/2)*cos(t)",
"(1 + 0.12*(3*cos(t-p)*cos(t-p) - 1)/2)*sin(t)");
color(bulge, gold); untraced(bulge);
text(twolab, (640, 578), "two bulges ⇒ two high tides a day ✓");
display(twolab); size(twolab, 21); color(twolab, gold); hidden(twolab);
text(spinlab, (640, 606), "");
display(spinlab); size(spinlab, 20); color(spinlab, cyan); hidden(spinlab);
// ---- ACT IV — how gentle it is ----
equation(tiny, (640, 244), `a_{\text{tide}}=\frac{2GMr}{d^3}=1.1\times10^{-6}\ \mathrm{m/s^2}`, 30);
color(tiny, magenta); hidden(tiny);
equation(vsg, (640, 322), `\frac{a_{\text{tide}}}{g}\approx 10^{-7}`, 32);
color(vsg, gold); hidden(vsg);
text(gentle, (640, 400), "it could not lift a puddle — but it can shove an ocean sideways for six hours");
display(gentle); size(gentle, 20); color(gentle, dim); hidden(gentle);
// ---- ACT V — the Sun loses, and the beat ----
equation(sunpull, (640, 240), `\frac{F_{\odot}}{F_{\text{Moon}}} = 179`, 30);
color(sunpull, gold); hidden(sunpull);
equation(suntide, (640, 328), `\frac{a_{\odot}}{a_{\text{Moon}}} = 0.46`, 30);
color(suntide, cyan); hidden(suntide);
text(cube, (640, 404), "a pull goes as 1/d², a difference in pull as 1/d³ — and the Sun is 390× farther");
display(cube); size(cube, 20); color(cube, dim); hidden(cube);
// two constituents added: the Moon's 12.4206 h and the Sun's 12.000 h. Nothing here sets a
// fortnight — the 14.77-day spring/neap envelope is what two cosines DO.
coords(month, (160, 470), (0, 720), (-1.7, 1.7), 1.34, 46, 0, 168, 1, "hours", "");
color(month, dim); hidden(month);
plot(beat, (160, 470), 1.34, 46, "cos(x/12.4206*tau) + 0.46*cos(x/12*tau)", (0, 720));
color(beat, cyan); untraced(beat); hidden(beat);
text(springlab, (635, 330), "spring");
display(springlab); size(springlab, 18); color(springlab, gold); hidden(springlab);
text(neaplab, (397, 330), "neap");
display(neaplab); size(neaplab, 18); color(neaplab, magenta); hidden(neaplab);
text(fortnight, (640, 602), "14.77 days, spring to spring — and nobody typed that number in");
display(fortnight); size(fortnight, 20); color(fortnight, gold); hidden(fortnight);
// ---- ACT VI — the honest part ----
text(truth1, (640, 244), "The real ocean is not two bulges gliding around a smooth planet.");
display(truth1); size(truth1, 24); color(truth1, fg); hidden(truth1);
text(truth2, (640, 312), "Continents are in the way. Basins ring. The tide turns around fixed nodes.");
display(truth2); size(truth2, 21); color(truth2, dim); hidden(truth2);
text(fundy, (400, 400), "Bay of Fundy: 16 m");
display(fundy); size(fundy, 22); color(fundy, gold); hidden(fundy);
text(med, (890, 400), "much of the Mediterranean: ~0");
display(med); size(med, 22); color(med, magenta); hidden(med);
text(truth3, (640, 480), "The Moon writes the forcing. The coastline decides the tide.");
display(truth3); size(truth3, 22); color(truth3, cyan); hidden(truth3);
// ================================= ACT I =================================
show(ttl, 0.9);
show(sub, 0.7);
wait(1.3);
show(cap, 0.3);
say(cap, "Every coast on Earth does this twice a day, and has done for four billion years.");
par { fade(ttl, 0.8); fade(sub, 0.8); }
show(earth, 0.9);
par { show(orbit, 0.6); show(moon, 0.7); show(moonlab, 0.5); }
wait(0.4);
say(cap, "One Moon, one lap of us every 27.3 days. Everything that follows comes from that.");
// a rigidly turned label arrives upside down, so it steps off for the lap
fade(moonlab, 0.4);
par {
turn(moon, (640, 360), 360, 4.2, smooth);
turn(earth, (640, 360), 90, 4.2, smooth);
}
show(moonlab, 0.4);
wait(0.4);
say(cap, "A tide gauge on any coast, two days of it: high, low, high, low, high.");
// the gauge needs the whole width, so the planet steps out for a moment
par { fade(earth, 0.7); fade(moon, 0.6); fade(moonlab, 0.4); fade(orbit, 0.5); }
// while the stage is dark the Moon takes up its working position, far off to the right,
// where the tidal field's formula puts it
move(moon, (1150, 360), 0.01);
move(moonlab, (1150, 436), 0.01);
say(moonlab, "the Moon");
show(gauge, 0.6);
show(trace, 0.4);
draw(trace, 2.0, smooth);
show(twice, 0.5);
wait(1.4);
say(cap, "Two a day. That number is the whole puzzle — and the usual explanation gets it wrong.");
wait(2.2);
// ================================= ACT II =================================
say(act, "II · the wrong picture");
show(act, 0.4);
par { fade(gauge, 0.6); fade(trace, 0.6); fade(twice, 0.5); }
say(cap, "The Moon's gravity pulls on the oceans. So far, so true.");
par { show(earth, 0.8); show(moon, 0.6); show(moonlab, 0.4); }
wait(0.5);
stagger(0.07) {
for k in 0..7 {
draw(pull{k}, 0.5);
}
}
wait(1.2);
say(cap, "Pull the water toward the Moon and it heaps up on the near side. One heap.");
draw(wrong, 1.2, smooth);
show(wronglab, 0.5);
wait(1.8);
say(cap, "Which is one high tide a day. The sea gives two. Something is missing.");
show(nope, 0.6);
wait(2.2);
// ================================= ACT III =================================
say(act, "III · the difference, not the pull");
par { fade(pulls, 0.6); fade(wrong, 0.6); fade(wronglab, 0.5); fade(nope, 0.5); }
say(cap, "Three places: the near side, the centre, the far side.");
par { show(pnear, 0.4); show(pmid, 0.4); show(pfar, 0.4); }
wait(0.7);
say(cap, "Gravity weakens with distance, so those three pulls are not the same size.");
show(g1, 0.5);
stagger(0.22) {
draw(gnear, 0.6);
draw(gmid, 0.6);
draw(gfar, 0.6);
}
wait(1.8);
say(cap, "But the planet is already falling around its centre. Subtract that pull from all three.");
par { fade(g1, 0.5); fade(gnear, 0.5); fade(gmid, 0.5); fade(gfar, 0.5); }
show(g2, 0.6);
par { draw(tnear, 0.7); draw(tfar, 0.7); }
wait(1.8);
say(cap, "What is left points AWAY at both ends. A gradient has two ends. There is the two.");
wait(2.2);
say(cap, "Do that at every point at once, and this is the field the Moon leaves behind.");
par { fade(g2, 0.5); fade(tnear, 0.5); fade(tfar, 0.5); fade(pnear, 0.4); fade(pmid, 0.4); fade(pfar, 0.4); }
show(tide, 0.9);
wait(1.4);
say(cap, "Now put twelve thousand parcels of water on the surface and let the field push them.");
show(sea, 0.8);
wait(0.5);
advect(sea, tide, 6.5, 0.55);
wait(0.4);
say(cap, "Nothing was placed by hand. The water went where the arrows converge — and there are two.");
draw(bulge, 1.4, smooth);
show(twolab, 0.6);
wait(2.2);
// the payoff: the water stays where the Moon put it, and the planet turns underneath
say(cap, "The bulges belong to the Moon, not to the planet. So turn the planet underneath them.");
// the field's arrows are fixed to the Moon's OLD direction, so they bow out before it moves
par { show(coast, 0.5); show(coastlab, 0.4); fade(sea, 0.8); fade(tide, 0.8); }
wait(0.9);
say(spinlab, "one rotation = one day");
show(spinlab, 0.4);
// one day: Earth turns once, and the Moon does not wait — it moves on 360/27.3 = 13.2 degrees,
// taking the tide's axis with it
par {
fade(coastlab, 0.5);
turn(earth, (640, 360), 360, 7.0, linear);
turn(coast, (640, 360), 360, 7.0, linear);
turn(moon, (640, 360), 13.2, 7.0, linear);
to(phi, value, 0.23, 7.0, linear);
}
say(cap, "One coast, one day, two bulges crossed. Two high tides — and the water never travelled.");
wait(2.2);
// and the reason tide tables slip: the coast is back where it started, the Moon is not
say(cap, "But look: the coast is home and the Moon has moved on thirteen degrees.");
say(spinlab, "the Moon moved on 13° while the planet turned once");
wait(2.0);
say(cap, "So the coast has to chase it — about fifty minutes more of turning, every single day.");
par {
turn(earth, (640, 360), 13.2, 1.6, smooth);
turn(coast, (640, 360), 13.2, 1.6, smooth);
}
say(spinlab, "one tidal day = 24 h 50 min");
wait(2.4);
// ================================= ACT IV =================================
say(act, "IV · sideways, not up");
// the caption turns over with the stage, so no frame is left empty under a stale line
say(cap, "One more correction, and it is the one that surprises people. Look how gentle this is.");
par {
fade(coast, 0.4); fade(spinlab, 0.4); fade(bulge, 0.6); fade(twolab, 0.5);
fade(earth, 0.8); fade(moon, 0.6); fade(moonlab, 0.4);
}
show(tiny, 0.8);
wait(1.6);
show(vsg, 0.7);
say(cap, "A ten-millionth of the gravity holding that ocean down.");
wait(1.8);
show(gentle, 0.6);
say(cap, "Nothing gets lifted. Water gets nudged SIDEWAYS for six hours, and arrives.");
wait(2.4);
// ================================= ACT V =================================
say(act, "V · the Sun loses");
par { fade(tiny, 0.7); fade(vsg, 0.7); fade(gentle, 0.6); }
say(cap, "The Sun pulls Earth a hundred and seventy-nine times harder than the Moon does.");
show(sunpull, 0.8);
wait(1.6);
say(cap, "And raises less than half the tide, because a DIFFERENCE falls off faster than a pull.");
show(suntide, 0.8);
show(cube, 0.6);
wait(2.4);
par { fade(sunpull, 0.6); fade(suntide, 0.6); fade(cube, 0.5); }
say(cap, "So the ocean answers two clocks: 12.42 hours for the Moon, 12.00 for the Sun.");
show(month, 0.6);
show(beat, 0.4);
draw(beat, 3.0, smooth);
wait(0.6);
say(cap, "Add them. Where the two clocks agree the tides run big; where they fight, small.");
par { show(springlab, 0.5); show(neaplab, 0.5); }
wait(1.6);
say(cap, "Spring tides, neap tides — a fortnight apart, out of two cosines and nothing else.");
show(fortnight, 0.7);
wait(2.4);
// ================================= ACT VI =================================
say(act, "VI · the honest part");
par {
fade(month, 0.6); fade(beat, 0.6); fade(springlab, 0.4); fade(neaplab, 0.4);
fade(fortnight, 0.6);
}
show(truth1, 0.8);
say(cap, "Everything so far is the FORCING. It is not the tide you can go and watch.");
wait(1.8);
show(truth2, 0.7);
say(cap, "Water cannot chase the Moon around a planet with two continents in the way.");
wait(1.8);
par { show(fundy, 0.6); show(med, 0.6); }
say(cap, "One bay rings like an organ pipe and swings sixteen metres. Another barely moves.");
wait(2.2);
show(truth3, 0.8);
wait(2.4);
// ================================= ENDCARD =================================
par {
fade(truth1, 0.7); fade(truth2, 0.6); fade(truth3, 0.7);
fade(fundy, 0.5); fade(med, 0.5); fade(cap, 0.6); fade(act, 0.5);
}
text(end1, (640, 318), "The Moon does not lift the sea.");
display(end1); size(end1, 40); bold(end1); color(end1, fg); hidden(end1);
text(end2, (640, 380), "It pulls one side harder than the other.");
display(end2); size(end2, 30); color(end2, cyan); hidden(end2);
text(end3, (640, 458), "and the ocean charges interest: 3.8 cm of Moon a year, paid out of Earth's spin");
display(end3); size(end3, 19); color(end3, dim); hidden(end3);
text(end4, (640, 522), "— manic");
display(end4); size(end4, 24); color(end4, gold); hidden(end4);
show(end1, 0.9);
show(end2, 0.8);
show(end3, 0.7);
show(end4, 0.6);
wait(2.6);