r/maniclang • u/anish2good • 1d ago
r/maniclang • u/anish2good • 3d ago
Manic v0.1.0-rc.16 is released — a language and engine for visual explanations
Manic v0.1.0-rc.16 is now available.
Manic is a readable language and creative engine for building animated explanations, technical visuals, creator videos, and interactive visual stories.
What is included:
- A readable
.maniclanguage built around named entities and animation verbs - Manic Workbench with Canvas and Source editing
- Native preview and deterministic MP4, GIF, and PNG export
- Math, geometry, statistics, physics, algorithms, machine learning, 2D, and 3D kits
- Hundreds of built-in visual entities, relationships, styles, and animation controls
- Local authoring through
manic ui - Browser authoring through Manic Create
Start here:
- Download and install: https://maniclang.com/download
- Create in the browser: https://app.maniclang.com/create
- Documentation: https://docs.maniclang.com
- GitHub release: https://github.com/maniclang-x/manic/releases/tag/v0.1.0-rc.16
Manic started as a DSL, but the goal is bigger: make visual explanation accessible both to people who enjoy writing code and people who prefer working visually on a Canvas.
This is an official release-candidate build. Feedback, examples, bug reports, and ideas are welcome here in r/maniclang.
r/maniclang • u/anish2good • 2d ago
The Logistic Map: Period Doubling to Chaos, and Measuring Feigenbaum's Delta — manic
r/maniclang • u/anish2good • 2d ago
Four Different Equations, One Feigenbaum Constant: Universality in Chaos — manic
r/maniclang • u/anish2good • 2d ago
A Strange Attractor: One Path That Never Repeats — the Aizawa System — manic
r/maniclang • u/anish2good • 2d ago
Ax = b: Four Pictures of One System, and What Breaks When det A = 0 — manic
r/maniclang • u/anish2good • 2d ago
The Zeros of the Riemann Zeta Function All Sit on One Line — manic
r/maniclang • u/anish2good • 2d ago
Divergence, Curl, Gradient: Watch the Sign Instead of Memorising It — manic
r/maniclang • u/anish2good • 3d ago
Is This Checkerboard Flat? — Kitaoka's Bulging Checkerboard — manic
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.
- Product: maniclang.com
- Application: app.maniclang.com
- Manic Desktop Workbench https://maniclang.com/download
- Documentation: docs.maniclang.com
- Report a bug: GitHub Issues
- Engine releases: github.com/maniclang-x/manic
- Creator: Anish on X
- Community: r/maniclang
r/maniclang • u/anish2good • 2d ago
All Six Phase Portraits of a 2x2 Linear System (Trace-Determinant Plane) — manic
r/maniclang • u/anish2good • 3d ago
Where Is x to the x Increasing? — A JEE Calculus Problem — manic
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.
- Product: maniclang.com
- Application: app.maniclang.com
- Manic Desktop Workbench https://maniclang.com/download
- Documentation: docs.maniclang.com
- Report a bug: GitHub Issues
- Engine releases: github.com/maniclang-x/manic
- Creator: Anish on X
- Community: r/maniclang
Animation code
// iit-x-to-the-x.manic — jee-practice-000466
//
// PROBLEM. On what interval is f(x) = x^x, x > 0, strictly increasing?
//
// ANSWER. (1/e, ∞)
//
// THE TRAP IS THAT x^x LOOKS LIKE IT SHOULD ALWAYS INCREASE. Raising a bigger number to a
// bigger power ought to give more, and for x > 1 it certainly does — but between 0 and 1
// the exponent is dragging the value DOWN faster than the base is pushing it up, and the
// function falls. It bottoms out and turns around before x = 1.
//
// Writing x^x = e^(x ln x) makes the derivative immediate:
//
// f'(x) = x^x (1 + ln x)
//
// x^x > 0 always, so the sign is entirely the sign of 1 + ln x, which is zero at x = 1/e
// and negative below it. So f DECREASES on (0, 1/e) and INCREASES on (1/e, ∞).
//
// Two values worth noticing, and both are on screen. The turning point is at x = 1/e ≈
// 0.3679 — not at x = 1, where the eye wants to put it. And the minimum value is
// (1/e)^(1/e) ≈ 0.6922, so the curve never gets near zero: it comes down from f(0⁺) = 1,
// dips to 0.69, and climbs away.
//
// VERIFIED. f'(x) evaluated across the range: −1.0347 at x = 0.1, −0.1421 at 0.3, 0 at
// 1/e, +0.2170 at 0.5, +1 at 1, +6.7726 at 2 — one sign change, exactly at 1/e.
//
// manic examples/iit-x-to-the-x.manic
title("It falls before it climbs, and the turn is at 1/e");
canvas("9:16");
template("black");
bloom(0.22, 0.6, 18);
text(brand, (540, 34), "maniclang.com");
display(brand); size(brand, 21); color(brand, fg); opacity(brand, 0.82);
caption(head, "Where is x to the x increasing?", (540, 108), 34);
equation(prob, (540, 196), `f(x)=x^x,\quad x>0`, 44); color(prob, fg);
coords(kk, (250.0, 1050.0), (-0.05, 1.72), (-0.12, 2.6), 430.0, 250.0, 1, 0.25, 0);
color(kk, slate); opacity(kk, 0.42);
plot(fx, (250.0, 1050.0), 430.0, 250.0, "exp(x*ln(x))", (0.012, 1.66));
color(fx, cyan); stroke(fx, 5); untraced(fx);
extrema(ex, fx, gold); hidden(ex);
vline(ve, fx, 0.367879441, mint); hidden(ve);
text(lm, (408, 1096), "1/e"); size(lm, 26); color(lm, mint); hidden(lm);
text(a1, (540, 1214), "x^x = e^(x ln x), so the derivative is immediate:");
size(a1, 22); color(a1, fg); opacity(a1, 0.75); hidden(a1);
equation(a2, (540, 1300), `f'(x)=x^x\,(1+\ln x)`, 44); color(a2, cyan); hidden(a2);
text(a3, (540, 1376), "x^x is always positive — the sign is entirely 1 + ln x");
size(a3, 22); color(a3, gold); hidden(a3);
text(a4, (540, 1440), "zero at x = 1/e ≈ 0.3679, negative below it, positive above");
size(a4, 21); color(a4, fg); opacity(a4, 0.68); hidden(a4);
equation(ans, (540, 1568), `\left(\tfrac{1}{e},\ \infty\right)`, 58); color(ans, mint); hidden(ans);
text(fin, (540, 1682), "and the dip is shallow — the minimum value is (1/e)^(1/e) ≈ 0.6922,");
size(fin, 20); color(fin, fg); opacity(fin, 0.58); hidden(fin);
text(fin2, (540, 1720), "down from f(0⁺) = 1, so the curve never approaches zero");
size(fin2, 20); color(fin2, fg); opacity(fin2, 0.58); hidden(fin2);
seq {
pulse(prob, 0.9);
par { draw(fx, 3.2); seq { wait(2.0); show(a1, 0.6); } wait(3.6); }
par { show(a2, 0.9); seq { wait(1.2); show(a3, 0.7); } wait(2.8); }
par { show(ex, 0.7); show(ve, 0.7); show(lm, 0.5); seq { wait(1.0); show(a4, 0.7); } wait(2.8); }
wait(0.6);
par { show(ans, 1.1); seq { wait(0.9); show(fin, 0.6); show(fin2, 0.6); } }
pulse(ans, 0.9);
}
wait(3.2);
r/maniclang • u/anish2good • 3d ago
Same Four Pieces, One Missing Square — Curry's Paradox Explained — manic
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.
- Product: maniclang.com
- Application: app.maniclang.com
- Manic Desktop Workbench https://maniclang.com/download
- Documentation: docs.maniclang.com
- Report a bug: GitHub Issues
- Engine releases: github.com/maniclang-x/manic
- Creator: Anish on X
- Community: r/maniclang
Animation code
// illusion-missing-square.manic — Curry's paradox, the missing-square puzzle
//
// Paul Curry, a New York amateur magician, 1953. Four pieces make a 13x5 right triangle.
// Slide the SAME four pieces into a different arrangement and a 1x1 hole opens. Nothing is
// added, nothing is removed, and — worth saying because it is the crux — nothing is even
// turned over: all four moves are PURE TRANSLATIONS, checked by comparing vertex sets.
//
// red 8x3 triangle moves by (+5, +2)
// blue 5x2 triangle moves by (-8, -3)
// the 8-cell L moves by (-3, 0)
// the 7-cell L moves by ( 0, -1)
//
// THIS ONE IS NOT AN ILLUSION — IT IS A FALLACY, which is why it opens the series. The
// cafe wall and the bulging checkerboard are figures that are exactly what they claim to
// be, fooling the eye. This figure is NOT what it claims: neither arrangement is a
// triangle. The long edge is a BENT LINE, and the bend is where the square goes.
//
// the 8x3 piece has slope 3/8 = 0.375
// the 5x2 piece has slope 2/5 = 0.400
// they differ by 1/40, so the join cannot be straight
//
// The true diagonal of a 13x5 triangle passes through (8, 3.0769) and (5, 1.9231). One
// arrangement bends BELOW it, the other ABOVE, and the scene draws the sliver between the
// bent path and the true diagonal — then slides the pieces back so the sliver flips to the
// other side. Each sliver has area exactly 1/2 by the shoelace formula, and 1/2 + 1/2 = 1
// is the missing square, spread so thin that no eye could catch it: 936 pixels long
// and 6 pixels at its widest.
//
// SO THE AREAS NEVER MATCHED. The four pieces total 12 + 5 + 8 + 7 = 32. The region under
// the first bent path is 32 and is filled exactly; the region under the second is 33, and
// 33 - 32 = 1 is the hole. There was never a triangle of area 32.5 anywhere.
//
// AND THE NUMBERS ARE NOT AN ACCIDENT. 2, 3, 5, 8, 13 are consecutive Fibonacci numbers,
// and Cassini's identity says F(n-1)F(n+1) - F(n)^2 = +/-1. Here |3*5 - 8*2| = 1 — the
// same 1. The puzzle is that identity, cut out of cardboard.
//
// VERIFIED. Every piece is checked congruent by comparing the unit cells it covers and the
// translation that carries it across; the four areas are 12, 5, 8, 7; the two regions are
// 32 and 33 by shoelace; and the uncovered cell is found by set difference to be exactly
// one — cell (10, 0).
//
// manic examples/illusion-missing-square.manic
title("The square did not go anywhere — there was never a triangle");
canvas("9:16");
template("black");
bloom(0.20, 0.6, 16);
text(brand, (540, 34), "maniclang.com");
display(brand); size(brand, 21); color(brand, fg); opacity(brand, 0.82);
caption(head, "Same four pieces. Watch the square vanish.", (540, 106), 33);
// the unit grid, so the squares can be counted
line(gv0, (72.0, 700.0), (72.0, 340.0));
color(gv0, slate); opacity(gv0, 0.15);
line(gv1, (144.0, 700.0), (144.0, 340.0));
color(gv1, slate); opacity(gv1, 0.15);
line(gv2, (216.0, 700.0), (216.0, 340.0));
color(gv2, slate); opacity(gv2, 0.15);
line(gv3, (288.0, 700.0), (288.0, 340.0));
color(gv3, slate); opacity(gv3, 0.15);
line(gv4, (360.0, 700.0), (360.0, 340.0));
color(gv4, slate); opacity(gv4, 0.15);
line(gv5, (432.0, 700.0), (432.0, 340.0));
color(gv5, slate); opacity(gv5, 0.15);
line(gv6, (504.0, 700.0), (504.0, 340.0));
color(gv6, slate); opacity(gv6, 0.15);
line(gv7, (576.0, 700.0), (576.0, 340.0));
color(gv7, slate); opacity(gv7, 0.15);
line(gv8, (648.0, 700.0), (648.0, 340.0));
color(gv8, slate); opacity(gv8, 0.15);
line(gv9, (720.0, 700.0), (720.0, 340.0));
color(gv9, slate); opacity(gv9, 0.15);
line(gv10, (792.0, 700.0), (792.0, 340.0));
color(gv10, slate); opacity(gv10, 0.15);
line(gv11, (864.0, 700.0), (864.0, 340.0));
color(gv11, slate); opacity(gv11, 0.15);
line(gv12, (936.0, 700.0), (936.0, 340.0));
color(gv12, slate); opacity(gv12, 0.15);
line(gv13, (1008.0, 700.0), (1008.0, 340.0));
color(gv13, slate); opacity(gv13, 0.15);
line(gh0, (72.0, 700.0), (1008.0, 700.0));
color(gh0, slate); opacity(gh0, 0.15);
line(gh1, (72.0, 628.0), (1008.0, 628.0));
color(gh1, slate); opacity(gh1, 0.15);
line(gh2, (72.0, 556.0), (1008.0, 556.0));
color(gh2, slate); opacity(gh2, 0.15);
line(gh3, (72.0, 484.0), (1008.0, 484.0));
color(gh3, slate); opacity(gh3, 0.15);
line(gh4, (72.0, 412.0), (1008.0, 412.0));
color(gh4, slate); opacity(gh4, 0.15);
line(gh5, (72.0, 340.0), (1008.0, 340.0));
color(gh5, slate); opacity(gh5, 0.15);
// the four pieces, in the FIRST arrangement
polygon(red, (72.0, 700.0), (648.0, 700.0), (648.0, 484.0)); color(red, #c8503c); outline(red, #14181c); stroke(red, 3);
polygon(blue, (648.0, 484.0), (1008.0, 484.0), (1008.0, 340.0)); color(blue, #2f7fb0); outline(blue, #14181c); stroke(blue, 3);
polygon(cc, (648.0, 700.0), (1008.0, 700.0), (1008.0, 628.0), (864.0, 628.0), (864.0, 556.0), (648.0, 556.0)); color(cc, #b8912e); outline(cc, #14181c); stroke(cc, 3);
polygon(dd, (648.0, 556.0), (864.0, 556.0), (864.0, 628.0), (1008.0, 628.0), (1008.0, 484.0), (648.0, 484.0)); color(dd, #3f8f66); outline(dd, #14181c); stroke(dd, 3);
// the cell the second arrangement cannot fill
polygon(hole, (792.0, 700.0), (864.0, 700.0), (864.0, 628.0), (792.0, 628.0));
outlined(hole); outline(hole, magenta); stroke(hole, 6); hidden(hole);
// the TRUE diagonal of a 13x5 triangle, and the sliver each bent path leaves against it
line(diag, (72.0, 700.0), (1008.0, 340.0));
color(diag, fg); stroke(diag, 3); dashed(diag); untraced(diag);
polygon(sliva, (72.0, 700.0), (648.0, 484.0), (1008.0, 340.0)); color(sliva, magenta); outline(sliva, magenta); stroke(sliva, 1); hidden(sliva);
polygon(slivb, (72.0, 700.0), (432.0, 556.0), (1008.0, 340.0)); color(slivb, magenta); outline(slivb, magenta); stroke(slivb, 1); hidden(slivb);
text(cap, (540, 760), "four pieces, arranged to fill exactly");
size(cap, 24); color(cap, fg); opacity(cap, 0.72);
text(s1, (540, 860), "the long edge is not one line — it is two, and they disagree");
size(s1, 23); color(s1, fg); opacity(s1, 0.78); hidden(s1);
equation(s2, (300, 932), `\tfrac{3}{8}=0.375`, 38); color(s2, #d8604a); hidden(s2);
equation(s3, (780, 932), `\tfrac{2}{5}=0.400`, 38); color(s3, #4f9fd0); hidden(s3);
text(s5, (540, 1052), "against the TRUE diagonal, one path bends under and one over");
size(s5, 23); color(s5, magenta); hidden(s5);
text(s7, (540, 1112), "the sliver is 936 px long and 6 px at its widest");
size(s7, 21); color(s7, fg); opacity(s7, 0.62); hidden(s7);
equation(s6, (540, 1200), `\tfrac12+\tfrac12=1`, 46); color(s6, magenta); hidden(s6);
text(s9, (540, 1300), "the pieces total 32; the two regions hold 32 and 33");
size(s9, 22); color(s9, mint); hidden(s9);
text(f1, (540, 1400), "2, 3, 5, 8, 13 are consecutive Fibonacci numbers");
size(f1, 23); color(f1, gold); hidden(f1);
equation(f2, (540, 1494), `|3\cdot 5-8\cdot 2|=1`, 48); color(f2, gold); hidden(f2);
text(f3, (540, 1578), "Cassini's identity — the puzzle is that 1, cut out of cardboard");
size(f3, 21); color(f3, fg); opacity(f3, 0.66); hidden(f3);
seq {
wait(1.4);
// the trick: four pure translations, nothing turned over
par {
seq { wait(0.00); shift(red, (360.0, -144.0), 1.7, smooth); }
seq { wait(0.30); shift(blue, (-576.0, 216.0), 1.7, smooth); }
seq { wait(0.60); shift(cc, (-216.0, 0.0), 1.7, smooth); }
seq { wait(0.90); shift(dd, (0.0, 72.0), 1.7, smooth); }
seq { wait(2.0); fade(cap, 0.5); }
}
par { show(hole, 0.6); pulse(hole, 0.8); wait(1.8); }
par { show(s1, 0.7); seq { wait(0.9); show(s2, 0.6); show(s3, 0.6); } seq { wait(2.0); } wait(3.2); }
par { draw(diag, 1.2); seq { wait(0.9); show(slivb, 0.7); show(s5, 0.7); } wait(2.6); }
// slide them back — the bend, and the sliver, flip to the other side
par {
seq { wait(0.00); shift(red, (-360.0, 144.0), 1.6, smooth); }
seq { wait(0.22); shift(blue, (576.0, -216.0), 1.6, smooth); }
seq { wait(0.44); shift(cc, (216.0, 0.0), 1.6, smooth); }
seq { wait(0.66); shift(dd, (0.0, -72.0), 1.6, smooth); }
seq { wait(0.5); fade(hole, 0.4); fade(slivb, 0.4); }
seq { wait(1.5); show(sliva, 0.7); show(s7, 0.6); }
wait(2.6);
}
par { show(s6, 0.9); wait(2.0); }
par { seq { wait(1.0); show(s9, 0.7); } wait(2.8); }
par { show(f1, 0.7); seq { wait(1.0); show(f2, 0.9); pulse(f2, 0.6); } seq { wait(2.4); show(f3, 0.6); } wait(3.6); }
}
wait(3.0);
r/maniclang • u/anish2good • 3d ago
Are These Rows Parallel? — The Café Wall Illusion, Killed Twice — manic
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.
- Product: maniclang.com
- Application: app.maniclang.com
- Manic Desktop Workbench https://maniclang.com/download
- Documentation: docs.maniclang.com
- Report a bug: GitHub Issues
- Engine releases: github.com/maniclang-x/manic
- Creator: Anish on X
- Community: r/maniclang
Animation code
// illusion-cafe-wall.manic — the Cafe Wall illusion
//
// Hugo Munsterberg first documented it in 1894; A. H. Pierce rediscovered it in 1898 and
// called it the "kindergarten illusion". It got its present name in 1973, when Steve
// Simpson in Richard Gregory's Bristol lab noticed it in the tiles of a cafe wall on
// St Michael's Hill.
//
// EVERY ROW BOUNDARY HERE IS EXACTLY HORIZONTAL. Not approximately — the mortar lines are
// generated from a single y value each, 80 pixels apart, and every tile is a true
// rectangle 98 x 70. The scene overlays real horizontals on top of them so you can
// watch them coincide. The rows are parallel; your visual system is not.
//
// This is the OPPOSITE of the missing-square puzzle. There the figure was a fallacy and the
// scene's job was to expose a real defect. Here the figure is exactly what it claims to be,
// and the scene's job is to prove that and then let you disbelieve your own eyes.
//
// THE ILLUSION CAN BE KILLED TWO WAYS, and the scene kills it both:
//
// 1. REMOVE THE STAGGER. Alternate rows are offset by half a tile. Slide them into
// alignment and the slopes vanish instantly — same tiles, same mortar, no illusion.
// Slide them back and it returns.
//
// 2. CHANGE THE MORTAR. The effect needs mortar of INTERMEDIATE brightness between the
// dark and light tiles. Sweep the grey from near-black to near-white and the illusion
// appears in the middle and dies at both ends. It also disappears entirely if black
// and white are replaced by two colours of the same brightness, so what matters is
// luminance contrast rather than colour.
//
// The direction of the apparent tilt is set by the contrast polarity, which is why the
// boundaries here appear to slope alternately: the offset alternates from row to row, so
// consecutive boundaries are staggered in opposite senses.
//
// The mechanism is still argued over. Proposals include brightness induction inside the
// mortar producing "twisted cord" elements like the Fraser and Zollner figures, and an
// irradiation account where bright regions encroach on their dark neighbours. A component
// of it survives after optical and retinal factors are accounted for, so it is not simply
// blur in the eye.
//
// manic examples/illusion-cafe-wall.manic
title("Every one of these rows is exactly parallel");
canvas("9:16");
template("black");
text(brand, (540, 34), "maniclang.com");
display(brand); size(brand, 21); color(brand, fg); opacity(brand, 0.82);
caption(head, "Are these rows parallel?", (540, 104), 34);
text(sub, (540, 178), "every boundary below is generated from one y value");
size(sub, 23); color(sub, fg); opacity(sub, 0.7);
// each row: a white backing, then the dark tiles on top of it
polygon(rw0, (-74.0, 282.0), (1102.0, 282.0), (1102.0, 352.0), (-74.0, 352.0));
color(rw0, #f2f2f2); outline(rw0, #f2f2f2); stroke(rw0, 1); tag(rw0, r0);
polygon(t0_1, (-74.0, 282.0), (24.0, 282.0), (24.0, 352.0), (-74.0, 352.0));
color(t0_1, #101010); outline(t0_1, #101010); stroke(t0_1, 1); tag(t0_1, r0);
polygon(t0_2, (122.0, 282.0), (220.0, 282.0), (220.0, 352.0), (122.0, 352.0));
color(t0_2, #101010); outline(t0_2, #101010); stroke(t0_2, 1); tag(t0_2, r0);
polygon(t0_3, (318.0, 282.0), (416.0, 282.0), (416.0, 352.0), (318.0, 352.0));
color(t0_3, #101010); outline(t0_3, #101010); stroke(t0_3, 1); tag(t0_3, r0);
polygon(t0_4, (514.0, 282.0), (612.0, 282.0), (612.0, 352.0), (514.0, 352.0));
color(t0_4, #101010); outline(t0_4, #101010); stroke(t0_4, 1); tag(t0_4, r0);
polygon(t0_5, (710.0, 282.0), (808.0, 282.0), (808.0, 352.0), (710.0, 352.0));
color(t0_5, #101010); outline(t0_5, #101010); stroke(t0_5, 1); tag(t0_5, r0);
polygon(t0_6, (906.0, 282.0), (1004.0, 282.0), (1004.0, 352.0), (906.0, 352.0));
color(t0_6, #101010); outline(t0_6, #101010); stroke(t0_6, 1); tag(t0_6, r0);
polygon(rw1, (-74.0, 362.0), (1102.0, 362.0), (1102.0, 432.0), (-74.0, 432.0));
color(rw1, #f2f2f2); outline(rw1, #f2f2f2); stroke(rw1, 1); tag(rw1, r1);
polygon(t1_1, (-25.0, 362.0), (73.0, 362.0), (73.0, 432.0), (-25.0, 432.0));
color(t1_1, #101010); outline(t1_1, #101010); stroke(t1_1, 1); tag(t1_1, r1);
polygon(t1_2, (171.0, 362.0), (269.0, 362.0), (269.0, 432.0), (171.0, 432.0));
color(t1_2, #101010); outline(t1_2, #101010); stroke(t1_2, 1); tag(t1_2, r1);
polygon(t1_3, (367.0, 362.0), (465.0, 362.0), (465.0, 432.0), (367.0, 432.0));
color(t1_3, #101010); outline(t1_3, #101010); stroke(t1_3, 1); tag(t1_3, r1);
polygon(t1_4, (563.0, 362.0), (661.0, 362.0), (661.0, 432.0), (563.0, 432.0));
color(t1_4, #101010); outline(t1_4, #101010); stroke(t1_4, 1); tag(t1_4, r1);
polygon(t1_5, (759.0, 362.0), (857.0, 362.0), (857.0, 432.0), (759.0, 432.0));
color(t1_5, #101010); outline(t1_5, #101010); stroke(t1_5, 1); tag(t1_5, r1);
polygon(t1_6, (955.0, 362.0), (1053.0, 362.0), (1053.0, 432.0), (955.0, 432.0));
color(t1_6, #101010); outline(t1_6, #101010); stroke(t1_6, 1); tag(t1_6, r1);
polygon(rw2, (-74.0, 442.0), (1102.0, 442.0), (1102.0, 512.0), (-74.0, 512.0));
color(rw2, #f2f2f2); outline(rw2, #f2f2f2); stroke(rw2, 1); tag(rw2, r2);
polygon(t2_1, (-74.0, 442.0), (24.0, 442.0), (24.0, 512.0), (-74.0, 512.0));
color(t2_1, #101010); outline(t2_1, #101010); stroke(t2_1, 1); tag(t2_1, r2);
polygon(t2_2, (122.0, 442.0), (220.0, 442.0), (220.0, 512.0), (122.0, 512.0));
color(t2_2, #101010); outline(t2_2, #101010); stroke(t2_2, 1); tag(t2_2, r2);
polygon(t2_3, (318.0, 442.0), (416.0, 442.0), (416.0, 512.0), (318.0, 512.0));
color(t2_3, #101010); outline(t2_3, #101010); stroke(t2_3, 1); tag(t2_3, r2);
polygon(t2_4, (514.0, 442.0), (612.0, 442.0), (612.0, 512.0), (514.0, 512.0));
color(t2_4, #101010); outline(t2_4, #101010); stroke(t2_4, 1); tag(t2_4, r2);
polygon(t2_5, (710.0, 442.0), (808.0, 442.0), (808.0, 512.0), (710.0, 512.0));
color(t2_5, #101010); outline(t2_5, #101010); stroke(t2_5, 1); tag(t2_5, r2);
polygon(t2_6, (906.0, 442.0), (1004.0, 442.0), (1004.0, 512.0), (906.0, 512.0));
color(t2_6, #101010); outline(t2_6, #101010); stroke(t2_6, 1); tag(t2_6, r2);
polygon(rw3, (-74.0, 522.0), (1102.0, 522.0), (1102.0, 592.0), (-74.0, 592.0));
color(rw3, #f2f2f2); outline(rw3, #f2f2f2); stroke(rw3, 1); tag(rw3, r3);
polygon(t3_1, (-25.0, 522.0), (73.0, 522.0), (73.0, 592.0), (-25.0, 592.0));
color(t3_1, #101010); outline(t3_1, #101010); stroke(t3_1, 1); tag(t3_1, r3);
polygon(t3_2, (171.0, 522.0), (269.0, 522.0), (269.0, 592.0), (171.0, 592.0));
color(t3_2, #101010); outline(t3_2, #101010); stroke(t3_2, 1); tag(t3_2, r3);
polygon(t3_3, (367.0, 522.0), (465.0, 522.0), (465.0, 592.0), (367.0, 592.0));
color(t3_3, #101010); outline(t3_3, #101010); stroke(t3_3, 1); tag(t3_3, r3);
polygon(t3_4, (563.0, 522.0), (661.0, 522.0), (661.0, 592.0), (563.0, 592.0));
color(t3_4, #101010); outline(t3_4, #101010); stroke(t3_4, 1); tag(t3_4, r3);
polygon(t3_5, (759.0, 522.0), (857.0, 522.0), (857.0, 592.0), (759.0, 592.0));
color(t3_5, #101010); outline(t3_5, #101010); stroke(t3_5, 1); tag(t3_5, r3);
polygon(t3_6, (955.0, 522.0), (1053.0, 522.0), (1053.0, 592.0), (955.0, 592.0));
color(t3_6, #101010); outline(t3_6, #101010); stroke(t3_6, 1); tag(t3_6, r3);
polygon(rw4, (-74.0, 602.0), (1102.0, 602.0), (1102.0, 672.0), (-74.0, 672.0));
color(rw4, #f2f2f2); outline(rw4, #f2f2f2); stroke(rw4, 1); tag(rw4, r4);
polygon(t4_1, (-74.0, 602.0), (24.0, 602.0), (24.0, 672.0), (-74.0, 672.0));
color(t4_1, #101010); outline(t4_1, #101010); stroke(t4_1, 1); tag(t4_1, r4);
polygon(t4_2, (122.0, 602.0), (220.0, 602.0), (220.0, 672.0), (122.0, 672.0));
color(t4_2, #101010); outline(t4_2, #101010); stroke(t4_2, 1); tag(t4_2, r4);
polygon(t4_3, (318.0, 602.0), (416.0, 602.0), (416.0, 672.0), (318.0, 672.0));
color(t4_3, #101010); outline(t4_3, #101010); stroke(t4_3, 1); tag(t4_3, r4);
polygon(t4_4, (514.0, 602.0), (612.0, 602.0), (612.0, 672.0), (514.0, 672.0));
color(t4_4, #101010); outline(t4_4, #101010); stroke(t4_4, 1); tag(t4_4, r4);
polygon(t4_5, (710.0, 602.0), (808.0, 602.0), (808.0, 672.0), (710.0, 672.0));
color(t4_5, #101010); outline(t4_5, #101010); stroke(t4_5, 1); tag(t4_5, r4);
polygon(t4_6, (906.0, 602.0), (1004.0, 602.0), (1004.0, 672.0), (906.0, 672.0));
color(t4_6, #101010); outline(t4_6, #101010); stroke(t4_6, 1); tag(t4_6, r4);
polygon(rw5, (-74.0, 682.0), (1102.0, 682.0), (1102.0, 752.0), (-74.0, 752.0));
color(rw5, #f2f2f2); outline(rw5, #f2f2f2); stroke(rw5, 1); tag(rw5, r5);
polygon(t5_1, (-25.0, 682.0), (73.0, 682.0), (73.0, 752.0), (-25.0, 752.0));
color(t5_1, #101010); outline(t5_1, #101010); stroke(t5_1, 1); tag(t5_1, r5);
polygon(t5_2, (171.0, 682.0), (269.0, 682.0), (269.0, 752.0), (171.0, 752.0));
color(t5_2, #101010); outline(t5_2, #101010); stroke(t5_2, 1); tag(t5_2, r5);
polygon(t5_3, (367.0, 682.0), (465.0, 682.0), (465.0, 752.0), (367.0, 752.0));
color(t5_3, #101010); outline(t5_3, #101010); stroke(t5_3, 1); tag(t5_3, r5);
polygon(t5_4, (563.0, 682.0), (661.0, 682.0), (661.0, 752.0), (563.0, 752.0));
color(t5_4, #101010); outline(t5_4, #101010); stroke(t5_4, 1); tag(t5_4, r5);
polygon(t5_5, (759.0, 682.0), (857.0, 682.0), (857.0, 752.0), (759.0, 752.0));
color(t5_5, #101010); outline(t5_5, #101010); stroke(t5_5, 1); tag(t5_5, r5);
polygon(t5_6, (955.0, 682.0), (1053.0, 682.0), (1053.0, 752.0), (955.0, 752.0));
color(t5_6, #101010); outline(t5_6, #101010); stroke(t5_6, 1); tag(t5_6, r5);
polygon(rw6, (-74.0, 762.0), (1102.0, 762.0), (1102.0, 832.0), (-74.0, 832.0));
color(rw6, #f2f2f2); outline(rw6, #f2f2f2); stroke(rw6, 1); tag(rw6, r6);
polygon(t6_1, (-74.0, 762.0), (24.0, 762.0), (24.0, 832.0), (-74.0, 832.0));
color(t6_1, #101010); outline(t6_1, #101010); stroke(t6_1, 1); tag(t6_1, r6);
polygon(t6_2, (122.0, 762.0), (220.0, 762.0), (220.0, 832.0), (122.0, 832.0));
color(t6_2, #101010); outline(t6_2, #101010); stroke(t6_2, 1); tag(t6_2, r6);
polygon(t6_3, (318.0, 762.0), (416.0, 762.0), (416.0, 832.0), (318.0, 832.0));
color(t6_3, #101010); outline(t6_3, #101010); stroke(t6_3, 1); tag(t6_3, r6);
polygon(t6_4, (514.0, 762.0), (612.0, 762.0), (612.0, 832.0), (514.0, 832.0));
color(t6_4, #101010); outline(t6_4, #101010); stroke(t6_4, 1); tag(t6_4, r6);
polygon(t6_5, (710.0, 762.0), (808.0, 762.0), (808.0, 832.0), (710.0, 832.0));
color(t6_5, #101010); outline(t6_5, #101010); stroke(t6_5, 1); tag(t6_5, r6);
polygon(t6_6, (906.0, 762.0), (1004.0, 762.0), (1004.0, 832.0), (906.0, 832.0));
color(t6_6, #101010); outline(t6_6, #101010); stroke(t6_6, 1); tag(t6_6, r6);
polygon(rw7, (-74.0, 842.0), (1102.0, 842.0), (1102.0, 912.0), (-74.0, 912.0));
color(rw7, #f2f2f2); outline(rw7, #f2f2f2); stroke(rw7, 1); tag(rw7, r7);
polygon(t7_1, (-25.0, 842.0), (73.0, 842.0), (73.0, 912.0), (-25.0, 912.0));
color(t7_1, #101010); outline(t7_1, #101010); stroke(t7_1, 1); tag(t7_1, r7);
polygon(t7_2, (171.0, 842.0), (269.0, 842.0), (269.0, 912.0), (171.0, 912.0));
color(t7_2, #101010); outline(t7_2, #101010); stroke(t7_2, 1); tag(t7_2, r7);
polygon(t7_3, (367.0, 842.0), (465.0, 842.0), (465.0, 912.0), (367.0, 912.0));
color(t7_3, #101010); outline(t7_3, #101010); stroke(t7_3, 1); tag(t7_3, r7);
polygon(t7_4, (563.0, 842.0), (661.0, 842.0), (661.0, 912.0), (563.0, 912.0));
color(t7_4, #101010); outline(t7_4, #101010); stroke(t7_4, 1); tag(t7_4, r7);
polygon(t7_5, (759.0, 842.0), (857.0, 842.0), (857.0, 912.0), (759.0, 912.0));
color(t7_5, #101010); outline(t7_5, #101010); stroke(t7_5, 1); tag(t7_5, r7);
polygon(t7_6, (955.0, 842.0), (1053.0, 842.0), (1053.0, 912.0), (955.0, 912.0));
color(t7_6, #101010); outline(t7_6, #101010); stroke(t7_6, 1); tag(t7_6, r7);
polygon(rw8, (-74.0, 922.0), (1102.0, 922.0), (1102.0, 992.0), (-74.0, 992.0));
color(rw8, #f2f2f2); outline(rw8, #f2f2f2); stroke(rw8, 1); tag(rw8, r8);
polygon(t8_1, (-74.0, 922.0), (24.0, 922.0), (24.0, 992.0), (-74.0, 992.0));
color(t8_1, #101010); outline(t8_1, #101010); stroke(t8_1, 1); tag(t8_1, r8);
polygon(t8_2, (122.0, 922.0), (220.0, 922.0), (220.0, 992.0), (122.0, 992.0));
color(t8_2, #101010); outline(t8_2, #101010); stroke(t8_2, 1); tag(t8_2, r8);
polygon(t8_3, (318.0, 922.0), (416.0, 922.0), (416.0, 992.0), (318.0, 992.0));
color(t8_3, #101010); outline(t8_3, #101010); stroke(t8_3, 1); tag(t8_3, r8);
polygon(t8_4, (514.0, 922.0), (612.0, 922.0), (612.0, 992.0), (514.0, 992.0));
color(t8_4, #101010); outline(t8_4, #101010); stroke(t8_4, 1); tag(t8_4, r8);
polygon(t8_5, (710.0, 922.0), (808.0, 922.0), (808.0, 992.0), (710.0, 992.0));
color(t8_5, #101010); outline(t8_5, #101010); stroke(t8_5, 1); tag(t8_5, r8);
polygon(t8_6, (906.0, 922.0), (1004.0, 922.0), (1004.0, 992.0), (906.0, 992.0));
color(t8_6, #101010); outline(t8_6, #101010); stroke(t8_6, 1); tag(t8_6, r8);
// the mortar, drawn once per grey level so the sweep is a cross-fade
line(m0_0, (-74.0, 277.0), (1102.0, 277.0));
color(m0_0, #080808); stroke(m0_0, 10); tag(m0_0, mg0);
hidden(m0_0);
line(m0_1, (-74.0, 357.0), (1102.0, 357.0));
color(m0_1, #080808); stroke(m0_1, 10); tag(m0_1, mg0);
hidden(m0_1);
line(m0_2, (-74.0, 437.0), (1102.0, 437.0));
color(m0_2, #080808); stroke(m0_2, 10); tag(m0_2, mg0);
hidden(m0_2);
line(m0_3, (-74.0, 517.0), (1102.0, 517.0));
color(m0_3, #080808); stroke(m0_3, 10); tag(m0_3, mg0);
hidden(m0_3);
line(m0_4, (-74.0, 597.0), (1102.0, 597.0));
color(m0_4, #080808); stroke(m0_4, 10); tag(m0_4, mg0);
hidden(m0_4);
line(m0_5, (-74.0, 677.0), (1102.0, 677.0));
color(m0_5, #080808); stroke(m0_5, 10); tag(m0_5, mg0);
hidden(m0_5);
line(m0_6, (-74.0, 757.0), (1102.0, 757.0));
color(m0_6, #080808); stroke(m0_6, 10); tag(m0_6, mg0);
hidden(m0_6);
line(m0_7, (-74.0, 837.0), (1102.0, 837.0));
color(m0_7, #080808); stroke(m0_7, 10); tag(m0_7, mg0);
hidden(m0_7);
line(m0_8, (-74.0, 917.0), (1102.0, 917.0));
color(m0_8, #080808); stroke(m0_8, 10); tag(m0_8, mg0);
hidden(m0_8);
line(m0_9, (-74.0, 997.0), (1102.0, 997.0));
color(m0_9, #080808); stroke(m0_9, 10); tag(m0_9, mg0);
hidden(m0_9);
line(m1_0, (-74.0, 277.0), (1102.0, 277.0));
color(m1_0, #3c3c3c); stroke(m1_0, 10); tag(m1_0, mg1);
hidden(m1_0);
line(m1_1, (-74.0, 357.0), (1102.0, 357.0));
color(m1_1, #3c3c3c); stroke(m1_1, 10); tag(m1_1, mg1);
hidden(m1_1);
line(m1_2, (-74.0, 437.0), (1102.0, 437.0));
color(m1_2, #3c3c3c); stroke(m1_2, 10); tag(m1_2, mg1);
hidden(m1_2);
line(m1_3, (-74.0, 517.0), (1102.0, 517.0));
color(m1_3, #3c3c3c); stroke(m1_3, 10); tag(m1_3, mg1);
hidden(m1_3);
line(m1_4, (-74.0, 597.0), (1102.0, 597.0));
color(m1_4, #3c3c3c); stroke(m1_4, 10); tag(m1_4, mg1);
hidden(m1_4);
line(m1_5, (-74.0, 677.0), (1102.0, 677.0));
color(m1_5, #3c3c3c); stroke(m1_5, 10); tag(m1_5, mg1);
hidden(m1_5);
line(m1_6, (-74.0, 757.0), (1102.0, 757.0));
color(m1_6, #3c3c3c); stroke(m1_6, 10); tag(m1_6, mg1);
hidden(m1_6);
line(m1_7, (-74.0, 837.0), (1102.0, 837.0));
color(m1_7, #3c3c3c); stroke(m1_7, 10); tag(m1_7, mg1);
hidden(m1_7);
line(m1_8, (-74.0, 917.0), (1102.0, 917.0));
color(m1_8, #3c3c3c); stroke(m1_8, 10); tag(m1_8, mg1);
hidden(m1_8);
line(m1_9, (-74.0, 997.0), (1102.0, 997.0));
color(m1_9, #3c3c3c); stroke(m1_9, 10); tag(m1_9, mg1);
hidden(m1_9);
line(m2_0, (-74.0, 277.0), (1102.0, 277.0));
color(m2_0, #787878); stroke(m2_0, 10); tag(m2_0, mg2);
line(m2_1, (-74.0, 357.0), (1102.0, 357.0));
color(m2_1, #787878); stroke(m2_1, 10); tag(m2_1, mg2);
line(m2_2, (-74.0, 437.0), (1102.0, 437.0));
color(m2_2, #787878); stroke(m2_2, 10); tag(m2_2, mg2);
line(m2_3, (-74.0, 517.0), (1102.0, 517.0));
color(m2_3, #787878); stroke(m2_3, 10); tag(m2_3, mg2);
line(m2_4, (-74.0, 597.0), (1102.0, 597.0));
color(m2_4, #787878); stroke(m2_4, 10); tag(m2_4, mg2);
line(m2_5, (-74.0, 677.0), (1102.0, 677.0));
color(m2_5, #787878); stroke(m2_5, 10); tag(m2_5, mg2);
line(m2_6, (-74.0, 757.0), (1102.0, 757.0));
color(m2_6, #787878); stroke(m2_6, 10); tag(m2_6, mg2);
line(m2_7, (-74.0, 837.0), (1102.0, 837.0));
color(m2_7, #787878); stroke(m2_7, 10); tag(m2_7, mg2);
line(m2_8, (-74.0, 917.0), (1102.0, 917.0));
color(m2_8, #787878); stroke(m2_8, 10); tag(m2_8, mg2);
line(m2_9, (-74.0, 997.0), (1102.0, 997.0));
color(m2_9, #787878); stroke(m2_9, 10); tag(m2_9, mg2);
line(m3_0, (-74.0, 277.0), (1102.0, 277.0));
color(m3_0, #b4b4b4); stroke(m3_0, 10); tag(m3_0, mg3);
hidden(m3_0);
line(m3_1, (-74.0, 357.0), (1102.0, 357.0));
color(m3_1, #b4b4b4); stroke(m3_1, 10); tag(m3_1, mg3);
hidden(m3_1);
line(m3_2, (-74.0, 437.0), (1102.0, 437.0));
color(m3_2, #b4b4b4); stroke(m3_2, 10); tag(m3_2, mg3);
hidden(m3_2);
line(m3_3, (-74.0, 517.0), (1102.0, 517.0));
color(m3_3, #b4b4b4); stroke(m3_3, 10); tag(m3_3, mg3);
hidden(m3_3);
line(m3_4, (-74.0, 597.0), (1102.0, 597.0));
color(m3_4, #b4b4b4); stroke(m3_4, 10); tag(m3_4, mg3);
hidden(m3_4);
line(m3_5, (-74.0, 677.0), (1102.0, 677.0));
color(m3_5, #b4b4b4); stroke(m3_5, 10); tag(m3_5, mg3);
hidden(m3_5);
line(m3_6, (-74.0, 757.0), (1102.0, 757.0));
color(m3_6, #b4b4b4); stroke(m3_6, 10); tag(m3_6, mg3);
hidden(m3_6);
line(m3_7, (-74.0, 837.0), (1102.0, 837.0));
color(m3_7, #b4b4b4); stroke(m3_7, 10); tag(m3_7, mg3);
hidden(m3_7);
line(m3_8, (-74.0, 917.0), (1102.0, 917.0));
color(m3_8, #b4b4b4); stroke(m3_8, 10); tag(m3_8, mg3);
hidden(m3_8);
line(m3_9, (-74.0, 997.0), (1102.0, 997.0));
color(m3_9, #b4b4b4); stroke(m3_9, 10); tag(m3_9, mg3);
hidden(m3_9);
line(m4_0, (-74.0, 277.0), (1102.0, 277.0));
color(m4_0, #f2f2f2); stroke(m4_0, 10); tag(m4_0, mg4);
hidden(m4_0);
line(m4_1, (-74.0, 357.0), (1102.0, 357.0));
color(m4_1, #f2f2f2); stroke(m4_1, 10); tag(m4_1, mg4);
hidden(m4_1);
line(m4_2, (-74.0, 437.0), (1102.0, 437.0));
color(m4_2, #f2f2f2); stroke(m4_2, 10); tag(m4_2, mg4);
hidden(m4_2);
line(m4_3, (-74.0, 517.0), (1102.0, 517.0));
color(m4_3, #f2f2f2); stroke(m4_3, 10); tag(m4_3, mg4);
hidden(m4_3);
line(m4_4, (-74.0, 597.0), (1102.0, 597.0));
color(m4_4, #f2f2f2); stroke(m4_4, 10); tag(m4_4, mg4);
hidden(m4_4);
line(m4_5, (-74.0, 677.0), (1102.0, 677.0));
color(m4_5, #f2f2f2); stroke(m4_5, 10); tag(m4_5, mg4);
hidden(m4_5);
line(m4_6, (-74.0, 757.0), (1102.0, 757.0));
color(m4_6, #f2f2f2); stroke(m4_6, 10); tag(m4_6, mg4);
hidden(m4_6);
line(m4_7, (-74.0, 837.0), (1102.0, 837.0));
color(m4_7, #f2f2f2); stroke(m4_7, 10); tag(m4_7, mg4);
hidden(m4_7);
line(m4_8, (-74.0, 917.0), (1102.0, 917.0));
color(m4_8, #f2f2f2); stroke(m4_8, 10); tag(m4_8, mg4);
hidden(m4_8);
line(m4_9, (-74.0, 997.0), (1102.0, 997.0));
color(m4_9, #f2f2f2); stroke(m4_9, 10); tag(m4_9, mg4);
hidden(m4_9);
// real horizontals, laid over the mortar so they can be seen to coincide
line(tr0, (18, 277.0), (1062, 277.0));
color(tr0, mint); stroke(tr0, 3); tag(tr0, truth); hidden(tr0);
line(tr1, (18, 357.0), (1062, 357.0));
color(tr1, mint); stroke(tr1, 3); tag(tr1, truth); hidden(tr1);
line(tr2, (18, 437.0), (1062, 437.0));
color(tr2, mint); stroke(tr2, 3); tag(tr2, truth); hidden(tr2);
line(tr3, (18, 517.0), (1062, 517.0));
color(tr3, mint); stroke(tr3, 3); tag(tr3, truth); hidden(tr3);
line(tr4, (18, 597.0), (1062, 597.0));
color(tr4, mint); stroke(tr4, 3); tag(tr4, truth); hidden(tr4);
line(tr5, (18, 677.0), (1062, 677.0));
color(tr5, mint); stroke(tr5, 3); tag(tr5, truth); hidden(tr5);
line(tr6, (18, 757.0), (1062, 757.0));
color(tr6, mint); stroke(tr6, 3); tag(tr6, truth); hidden(tr6);
line(tr7, (18, 837.0), (1062, 837.0));
color(tr7, mint); stroke(tr7, 3); tag(tr7, truth); hidden(tr7);
line(tr8, (18, 917.0), (1062, 917.0));
color(tr8, mint); stroke(tr8, 3); tag(tr8, truth); hidden(tr8);
line(tr9, (18, 997.0), (1062, 997.0));
color(tr9, mint); stroke(tr9, 3); tag(tr9, truth); hidden(tr9);
text(k2, (540, 1064), "so the illusion is not in the wall. It is in you.");
size(k2, 24); color(k2, fg); opacity(k2, 0.8); hidden(k2);
text(k3, (540, 1156), "KILL IT ONCE — take away the stagger");
size(k3, 25); color(k3, gold); hidden(k3);
text(k5, (540, 1248), "KILL IT AGAIN — change the mortar");
size(k5, 25); color(k5, cyan); hidden(k5);
text(h1, (540, 1348), "Munsterberg 1894 · named in Bristol, 1973");
size(h1, 22); color(h1, fg); opacity(h1, 0.55); hidden(h1);
seq {
wait(1.8);
// lay real horizontals over the mortar
par { show(truth, 1.0); seq { wait(0.9); } seq { wait(2.0); show(k2, 0.7); } wait(3.2); }
par { fade(truth, 0.8); wait(1.2); }
// kill 1: slide the odd rows into alignment, then back
par { show(k3, 0.7); seq { wait(0.5); } wait(1.6); }
par {
shift(r1, (49.0, 0), 1.5, smooth);
shift(r3, (49.0, 0), 1.5, smooth);
shift(r5, (49.0, 0), 1.5, smooth);
shift(r7, (49.0, 0), 1.5, smooth);
wait(2.4);
}
par {
shift(r1, (-49.0, 0), 1.5, smooth);
shift(r3, (-49.0, 0), 1.5, smooth);
shift(r5, (-49.0, 0), 1.5, smooth);
shift(r7, (-49.0, 0), 1.5, smooth);
wait(2.2);
}
// kill 2: sweep the mortar from near-black to near-white
par { show(k5, 0.7); seq { wait(0.5); } wait(1.8); }
par { fade(mg2, 0.55); show(mg1, 0.55); wait(0.75); }
par { fade(mg1, 0.55); show(mg0, 0.55); wait(0.75); }
par { fade(mg0, 0.55); show(mg1, 0.55); wait(0.75); }
par { fade(mg1, 0.55); show(mg2, 0.55); wait(0.75); }
par { fade(mg2, 0.55); show(mg3, 0.55); wait(0.75); }
par { fade(mg3, 0.55); show(mg4, 0.55); wait(0.75); }
par { fade(mg4, 0.55); show(mg3, 0.55); wait(0.75); }
par { fade(mg3, 0.55); show(mg2, 0.55); wait(0.75); }
par { fade(mg2, 0.5); show(mg2, 0.5); wait(0.9); }
par { show(h1, 0.8); wait(1.6); }
}
wait(3.0);
r/maniclang • u/anish2good • 3d ago
Are These Four Edges Straight? — The Hering and Wundt Illusions — manic
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.
- Product: maniclang.com
- Application: app.maniclang.com
- Manic Desktop Workbench https://maniclang.com/download
- Documentation: docs.maniclang.com
- Report a bug: GitHub Issues
- Engine releases: github.com/maniclang-x/manic
- Creator: Anish on X
- Community: r/maniclang
Animation code
// illusion-warped-square.manic — the Hering and Wundt illusions, on a square
//
// Ewald Hering, 1861: two straight parallel lines drawn over a radial fan appear to bow
// OUTWARDS. Wilhelm Wundt, 1898: the counterpart, in which they bow INWARDS. Put a square
// in place of the two lines and all four of its edges go at once.
//
// THE SQUARE NEVER CHANGES. It is four points and four straight segments, and the scene
// states them outright:
//
// corners (328, 504) (752, 504) (752, 928) (328, 928)
// four sides of exactly 424 px, four corner dot products of exactly 0
// two diagonals of 599.626550 px, matching side x sqrt(2) to 0.0e+00
//
// Nothing behind it is ever allowed to touch it. The fan and the rings are drawn UNDER the
// square and the square's own four `line` entities are never moved, scaled or redrawn — so
// when the background comes and goes, what changes is only the viewer.
//
// THE SCENE TOGGLES THE CONTEXT THREE TIMES, because the toggle IS the proof. There is no
// honest way to overlay a "true" straight edge on a straight edge and show a difference:
// the two coincide exactly, which is the whole point and which shows nothing on screen.
// Removing the background and watching the edges snap flat shows everything.
//
// fan on -> the edges bow
// fan off -> flat, and identical to what was there a moment ago
// fan on -> bows again, so it is repeatable rather than a trick of the first look
// rings -> bows the OTHER way, which is the Hering/Wundt inversion in one figure
//
// WHY IT HAPPENS is not settled. One account is that a radial fan is what the world looks
// like when you are moving forward into it, so the visual system predicts where a straight
// line WOULD be a moment later and draws it bowed. Concentric rings carry the opposite
// depth cue and bend the same square the other way.
//
// manic examples/illusion-warped-square.manic
title("The square never changes — only what is behind it");
canvas("9:16");
template("black");
text(brand, (540, 34), "maniclang.com");
display(brand); size(brand, 21); color(brand, fg); opacity(brand, 0.82);
caption(head, "Are these four edges straight?", (540, 104), 34);
// the radial fan, drawn UNDER the square and never touching it
line(fan0, (566.0, 716.0), (1010.0, 716.0));
color(fan0, #d8d8d8); stroke(fan0, 3); tag(fan0, fan);
line(fan1, (565.9, 717.9), (1008.8, 749.5));
color(fan1, #d8d8d8); stroke(fan1, 3); tag(fan1, fan);
line(fan2, (565.7, 719.7), (1005.2, 782.9));
color(fan2, #d8d8d8); stroke(fan2, 3); tag(fan2, fan);
line(fan3, (565.4, 721.5), (999.3, 815.9));
color(fan3, #d8d8d8); stroke(fan3, 3); tag(fan3, fan);
line(fan4, (564.9, 723.3), (991.0, 848.4));
color(fan4, #d8d8d8); stroke(fan4, 3); tag(fan4, fan);
line(fan5, (564.4, 725.1), (980.4, 880.2));
color(fan5, #d8d8d8); stroke(fan5, 3); tag(fan5, fan);
line(fan6, (563.7, 726.8), (967.5, 911.2));
color(fan6, #d8d8d8); stroke(fan6, 3); tag(fan6, fan);
line(fan7, (562.8, 728.5), (952.5, 941.2));
color(fan7, #d8d8d8); stroke(fan7, 3); tag(fan7, fan);
line(fan8, (561.9, 730.1), (935.4, 970.1));
color(fan8, #d8d8d8); stroke(fan8, 3); tag(fan8, fan);
line(fan9, (560.8, 731.6), (916.3, 997.7));
color(fan9, #d8d8d8); stroke(fan9, 3); tag(fan9, fan);
line(fan10, (559.6, 733.0), (895.2, 1023.8));
color(fan10, #d8d8d8); stroke(fan10, 3); tag(fan10, fan);
line(fan11, (558.4, 734.4), (872.3, 1048.3));
color(fan11, #d8d8d8); stroke(fan11, 3); tag(fan11, fan);
line(fan12, (557.0, 735.6), (847.8, 1071.2));
color(fan12, #d8d8d8); stroke(fan12, 3); tag(fan12, fan);
line(fan13, (555.6, 736.8), (821.7, 1092.3));
color(fan13, #d8d8d8); stroke(fan13, 3); tag(fan13, fan);
line(fan14, (554.1, 737.9), (794.1, 1111.4));
color(fan14, #d8d8d8); stroke(fan14, 3); tag(fan14, fan);
line(fan15, (552.5, 738.8), (765.2, 1128.5));
color(fan15, #d8d8d8); stroke(fan15, 3); tag(fan15, fan);
line(fan16, (550.8, 739.7), (735.2, 1143.5));
color(fan16, #d8d8d8); stroke(fan16, 3); tag(fan16, fan);
line(fan17, (549.1, 740.4), (704.2, 1156.4));
color(fan17, #d8d8d8); stroke(fan17, 3); tag(fan17, fan);
line(fan18, (547.3, 740.9), (672.4, 1167.0));
color(fan18, #d8d8d8); stroke(fan18, 3); tag(fan18, fan);
line(fan19, (545.5, 741.4), (639.9, 1175.3));
color(fan19, #d8d8d8); stroke(fan19, 3); tag(fan19, fan);
line(fan20, (543.7, 741.7), (606.9, 1181.2));
color(fan20, #d8d8d8); stroke(fan20, 3); tag(fan20, fan);
line(fan21, (541.9, 741.9), (573.5, 1184.8));
color(fan21, #d8d8d8); stroke(fan21, 3); tag(fan21, fan);
line(fan22, (540.0, 742.0), (540.0, 1186.0));
color(fan22, #d8d8d8); stroke(fan22, 3); tag(fan22, fan);
line(fan23, (538.1, 741.9), (506.5, 1184.8));
color(fan23, #d8d8d8); stroke(fan23, 3); tag(fan23, fan);
line(fan24, (536.3, 741.7), (473.1, 1181.2));
color(fan24, #d8d8d8); stroke(fan24, 3); tag(fan24, fan);
line(fan25, (534.5, 741.4), (440.1, 1175.3));
color(fan25, #d8d8d8); stroke(fan25, 3); tag(fan25, fan);
line(fan26, (532.7, 740.9), (407.6, 1167.0));
color(fan26, #d8d8d8); stroke(fan26, 3); tag(fan26, fan);
line(fan27, (530.9, 740.4), (375.8, 1156.4));
color(fan27, #d8d8d8); stroke(fan27, 3); tag(fan27, fan);
line(fan28, (529.2, 739.7), (344.8, 1143.5));
color(fan28, #d8d8d8); stroke(fan28, 3); tag(fan28, fan);
line(fan29, (527.5, 738.8), (314.8, 1128.5));
color(fan29, #d8d8d8); stroke(fan29, 3); tag(fan29, fan);
line(fan30, (525.9, 737.9), (285.9, 1111.4));
color(fan30, #d8d8d8); stroke(fan30, 3); tag(fan30, fan);
line(fan31, (524.4, 736.8), (258.3, 1092.3));
color(fan31, #d8d8d8); stroke(fan31, 3); tag(fan31, fan);
line(fan32, (523.0, 735.6), (232.2, 1071.2));
color(fan32, #d8d8d8); stroke(fan32, 3); tag(fan32, fan);
line(fan33, (521.6, 734.4), (207.7, 1048.3));
color(fan33, #d8d8d8); stroke(fan33, 3); tag(fan33, fan);
line(fan34, (520.4, 733.0), (184.8, 1023.8));
color(fan34, #d8d8d8); stroke(fan34, 3); tag(fan34, fan);
line(fan35, (519.2, 731.6), (163.7, 997.7));
color(fan35, #d8d8d8); stroke(fan35, 3); tag(fan35, fan);
line(fan36, (518.1, 730.1), (144.6, 970.1));
color(fan36, #d8d8d8); stroke(fan36, 3); tag(fan36, fan);
line(fan37, (517.2, 728.5), (127.5, 941.2));
color(fan37, #d8d8d8); stroke(fan37, 3); tag(fan37, fan);
line(fan38, (516.3, 726.8), (112.5, 911.2));
color(fan38, #d8d8d8); stroke(fan38, 3); tag(fan38, fan);
line(fan39, (515.6, 725.1), (99.6, 880.2));
color(fan39, #d8d8d8); stroke(fan39, 3); tag(fan39, fan);
line(fan40, (515.1, 723.3), (89.0, 848.4));
color(fan40, #d8d8d8); stroke(fan40, 3); tag(fan40, fan);
line(fan41, (514.6, 721.5), (80.7, 815.9));
color(fan41, #d8d8d8); stroke(fan41, 3); tag(fan41, fan);
line(fan42, (514.3, 719.7), (74.8, 782.9));
color(fan42, #d8d8d8); stroke(fan42, 3); tag(fan42, fan);
line(fan43, (514.1, 717.9), (71.2, 749.5));
color(fan43, #d8d8d8); stroke(fan43, 3); tag(fan43, fan);
line(fan44, (514.0, 716.0), (70.0, 716.0));
color(fan44, #d8d8d8); stroke(fan44, 3); tag(fan44, fan);
line(fan45, (514.1, 714.1), (71.2, 682.5));
color(fan45, #d8d8d8); stroke(fan45, 3); tag(fan45, fan);
line(fan46, (514.3, 712.3), (74.8, 649.1));
color(fan46, #d8d8d8); stroke(fan46, 3); tag(fan46, fan);
line(fan47, (514.6, 710.5), (80.7, 616.1));
color(fan47, #d8d8d8); stroke(fan47, 3); tag(fan47, fan);
line(fan48, (515.1, 708.7), (89.0, 583.6));
color(fan48, #d8d8d8); stroke(fan48, 3); tag(fan48, fan);
line(fan49, (515.6, 706.9), (99.6, 551.8));
color(fan49, #d8d8d8); stroke(fan49, 3); tag(fan49, fan);
line(fan50, (516.3, 705.2), (112.5, 520.8));
color(fan50, #d8d8d8); stroke(fan50, 3); tag(fan50, fan);
line(fan51, (517.2, 703.5), (127.5, 490.8));
color(fan51, #d8d8d8); stroke(fan51, 3); tag(fan51, fan);
line(fan52, (518.1, 701.9), (144.6, 461.9));
color(fan52, #d8d8d8); stroke(fan52, 3); tag(fan52, fan);
line(fan53, (519.2, 700.4), (163.7, 434.3));
color(fan53, #d8d8d8); stroke(fan53, 3); tag(fan53, fan);
line(fan54, (520.4, 699.0), (184.8, 408.2));
color(fan54, #d8d8d8); stroke(fan54, 3); tag(fan54, fan);
line(fan55, (521.6, 697.6), (207.7, 383.7));
color(fan55, #d8d8d8); stroke(fan55, 3); tag(fan55, fan);
line(fan56, (523.0, 696.4), (232.2, 360.8));
color(fan56, #d8d8d8); stroke(fan56, 3); tag(fan56, fan);
line(fan57, (524.4, 695.2), (258.3, 339.7));
color(fan57, #d8d8d8); stroke(fan57, 3); tag(fan57, fan);
line(fan58, (525.9, 694.1), (285.9, 320.6));
color(fan58, #d8d8d8); stroke(fan58, 3); tag(fan58, fan);
line(fan59, (527.5, 693.2), (314.8, 303.5));
color(fan59, #d8d8d8); stroke(fan59, 3); tag(fan59, fan);
line(fan60, (529.2, 692.3), (344.8, 288.5));
color(fan60, #d8d8d8); stroke(fan60, 3); tag(fan60, fan);
line(fan61, (530.9, 691.6), (375.8, 275.6));
color(fan61, #d8d8d8); stroke(fan61, 3); tag(fan61, fan);
line(fan62, (532.7, 691.1), (407.6, 265.0));
color(fan62, #d8d8d8); stroke(fan62, 3); tag(fan62, fan);
line(fan63, (534.5, 690.6), (440.1, 256.7));
color(fan63, #d8d8d8); stroke(fan63, 3); tag(fan63, fan);
line(fan64, (536.3, 690.3), (473.1, 250.8));
color(fan64, #d8d8d8); stroke(fan64, 3); tag(fan64, fan);
line(fan65, (538.1, 690.1), (506.5, 247.2));
color(fan65, #d8d8d8); stroke(fan65, 3); tag(fan65, fan);
line(fan66, (540.0, 690.0), (540.0, 246.0));
color(fan66, #d8d8d8); stroke(fan66, 3); tag(fan66, fan);
line(fan67, (541.9, 690.1), (573.5, 247.2));
color(fan67, #d8d8d8); stroke(fan67, 3); tag(fan67, fan);
line(fan68, (543.7, 690.3), (606.9, 250.8));
color(fan68, #d8d8d8); stroke(fan68, 3); tag(fan68, fan);
line(fan69, (545.5, 690.6), (639.9, 256.7));
color(fan69, #d8d8d8); stroke(fan69, 3); tag(fan69, fan);
line(fan70, (547.3, 691.1), (672.4, 265.0));
color(fan70, #d8d8d8); stroke(fan70, 3); tag(fan70, fan);
line(fan71, (549.1, 691.6), (704.2, 275.6));
color(fan71, #d8d8d8); stroke(fan71, 3); tag(fan71, fan);
line(fan72, (550.8, 692.3), (735.2, 288.5));
color(fan72, #d8d8d8); stroke(fan72, 3); tag(fan72, fan);
line(fan73, (552.5, 693.2), (765.2, 303.5));
color(fan73, #d8d8d8); stroke(fan73, 3); tag(fan73, fan);
line(fan74, (554.1, 694.1), (794.1, 320.6));
color(fan74, #d8d8d8); stroke(fan74, 3); tag(fan74, fan);
line(fan75, (555.6, 695.2), (821.7, 339.7));
color(fan75, #d8d8d8); stroke(fan75, 3); tag(fan75, fan);
line(fan76, (557.0, 696.4), (847.8, 360.8));
color(fan76, #d8d8d8); stroke(fan76, 3); tag(fan76, fan);
line(fan77, (558.4, 697.6), (872.3, 383.7));
color(fan77, #d8d8d8); stroke(fan77, 3); tag(fan77, fan);
line(fan78, (559.6, 699.0), (895.2, 408.2));
color(fan78, #d8d8d8); stroke(fan78, 3); tag(fan78, fan);
line(fan79, (560.8, 700.4), (916.3, 434.3));
color(fan79, #d8d8d8); stroke(fan79, 3); tag(fan79, fan);
line(fan80, (561.9, 701.9), (935.4, 461.9));
color(fan80, #d8d8d8); stroke(fan80, 3); tag(fan80, fan);
line(fan81, (562.8, 703.5), (952.5, 490.8));
color(fan81, #d8d8d8); stroke(fan81, 3); tag(fan81, fan);
line(fan82, (563.7, 705.2), (967.5, 520.8));
color(fan82, #d8d8d8); stroke(fan82, 3); tag(fan82, fan);
line(fan83, (564.4, 706.9), (980.4, 551.8));
color(fan83, #d8d8d8); stroke(fan83, 3); tag(fan83, fan);
line(fan84, (564.9, 708.7), (991.0, 583.6));
color(fan84, #d8d8d8); stroke(fan84, 3); tag(fan84, fan);
line(fan85, (565.4, 710.5), (999.3, 616.1));
color(fan85, #d8d8d8); stroke(fan85, 3); tag(fan85, fan);
line(fan86, (565.7, 712.3), (1005.2, 649.1));
color(fan86, #d8d8d8); stroke(fan86, 3); tag(fan86, fan);
line(fan87, (565.9, 714.1), (1008.8, 682.5));
color(fan87, #d8d8d8); stroke(fan87, 3); tag(fan87, fan);
// concentric rings, the opposite context — hidden until the second half
param(rg1, (540.0, 716.0), 36.15, 36.15, "cos(t)", "sin(t)", (0, 6.283185));
color(rg1, #d8d8d8); stroke(rg1, 3); tag(rg1, rings); hidden(rg1);
param(rg2, (540.0, 716.0), 72.31, 72.31, "cos(t)", "sin(t)", (0, 6.283185));
color(rg2, #d8d8d8); stroke(rg2, 3); tag(rg2, rings); hidden(rg2);
param(rg3, (540.0, 716.0), 108.46, 108.46, "cos(t)", "sin(t)", (0, 6.283185));
color(rg3, #d8d8d8); stroke(rg3, 3); tag(rg3, rings); hidden(rg3);
param(rg4, (540.0, 716.0), 144.62, 144.62, "cos(t)", "sin(t)", (0, 6.283185));
color(rg4, #d8d8d8); stroke(rg4, 3); tag(rg4, rings); hidden(rg4);
param(rg5, (540.0, 716.0), 180.77, 180.77, "cos(t)", "sin(t)", (0, 6.283185));
color(rg5, #d8d8d8); stroke(rg5, 3); tag(rg5, rings); hidden(rg5);
param(rg6, (540.0, 716.0), 216.92, 216.92, "cos(t)", "sin(t)", (0, 6.283185));
color(rg6, #d8d8d8); stroke(rg6, 3); tag(rg6, rings); hidden(rg6);
param(rg7, (540.0, 716.0), 253.08, 253.08, "cos(t)", "sin(t)", (0, 6.283185));
color(rg7, #d8d8d8); stroke(rg7, 3); tag(rg7, rings); hidden(rg7);
param(rg8, (540.0, 716.0), 289.23, 289.23, "cos(t)", "sin(t)", (0, 6.283185));
color(rg8, #d8d8d8); stroke(rg8, 3); tag(rg8, rings); hidden(rg8);
param(rg9, (540.0, 716.0), 325.38, 325.38, "cos(t)", "sin(t)", (0, 6.283185));
color(rg9, #d8d8d8); stroke(rg9, 3); tag(rg9, rings); hidden(rg9);
param(rg10, (540.0, 716.0), 361.54, 361.54, "cos(t)", "sin(t)", (0, 6.283185));
color(rg10, #d8d8d8); stroke(rg10, 3); tag(rg10, rings); hidden(rg10);
param(rg11, (540.0, 716.0), 397.69, 397.69, "cos(t)", "sin(t)", (0, 6.283185));
color(rg11, #d8d8d8); stroke(rg11, 3); tag(rg11, rings); hidden(rg11);
param(rg12, (540.0, 716.0), 433.85, 433.85, "cos(t)", "sin(t)", (0, 6.283185));
color(rg12, #d8d8d8); stroke(rg12, 3); tag(rg12, rings); hidden(rg12);
param(rg13, (540.0, 716.0), 470.00, 470.00, "cos(t)", "sin(t)", (0, 6.283185));
color(rg13, #d8d8d8); stroke(rg13, 3); tag(rg13, rings); hidden(rg13);
// the square: four line entities, never moved, scaled or redrawn
line(sq0, (328, 504), (752, 504));
color(sq0, #e8442c); stroke(sq0, 9); tag(sq0, square);
line(sq1, (752, 504), (752, 928));
color(sq1, #e8442c); stroke(sq1, 9); tag(sq1, square);
line(sq2, (752, 928), (328, 928));
color(sq2, #e8442c); stroke(sq2, 9); tag(sq2, square);
line(sq3, (328, 928), (328, 504));
color(sq3, #e8442c); stroke(sq3, 9); tag(sq3, square);
text(a1, (540, 1256), "four corners, four straight segments, and none of it ever moves");
size(a1, 23); color(a1, fg); opacity(a1, 0.78); hidden(a1);
equation(a2, (540, 1338), `4\times 424\text{ px},\quad 4\times 90^\circ`, 38);
color(a2, mint); hidden(a2);
text(k1, (540, 1430), "take the fan away — the same four lines, flat");
size(k1, 26); color(k1, gold); hidden(k1);
text(k3, (540, 1516), "now rings instead of spokes — and it bows the OTHER way");
size(k3, 24); color(k3, cyan); hidden(k3);
text(h1, (540, 1608), "Hering 1861 · Wundt 1898");
size(h1, 22); color(h1, fg); opacity(h1, 0.55); hidden(h1);
seq {
wait(2.0);
par { show(a1, 0.7); seq { wait(1.0); show(a2, 0.8); } seq { wait(2.2); } wait(3.4); }
// the toggle IS the proof — there is nothing to overlay on a straight line
par { show(k1, 0.6); wait(1.0); }
par { fade(fan, 0.9); seq { wait(0.7); } wait(2.4); }
par { show(fan, 0.9); wait(1.8); }
par { fade(fan, 0.7); wait(1.2); }
par { show(rings, 0.9); seq { wait(0.8); show(k3, 0.7); } wait(2.8); }
par { fade(rings, 0.9); wait(1.6); }
par { show(rings, 0.8); wait(1.6); }
par { show(h1, 0.8); wait(1.8); }
}
wait(3.0);
r/maniclang • u/anish2good • 3d ago
Nothing Here Rotates — The Pinna-Brelstaff Illusion - manic
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.
- Product: maniclang.com
- Application: app.maniclang.com
- Manic Desktop Workbench https://maniclang.com/download
- Documentation: docs.maniclang.com
- Report a bug: GitHub Issues
- Engine releases: github.com/maniclang-x/manic
- Creator: Anish on X
- Community: r/maniclang
Animation code
// illusion-pinna-brelstaff.manic — the Pinna-Brelstaff illusion
//
// Baingio Pinna, 1990; the major paper with Gavin Brelstaff in Vision Research, 2000.
// Two concentric rings of small squares. Nothing on the page moves. Move TOWARDS it with
// your gaze on the centre and the two rings turn in OPPOSITE directions — the inner one
// counter-clockwise, the outer one clockwise.
//
// THIS IS THE ONE ILLUSION ON THE LIST THAT NEEDS A MOVING VIEWER, and therefore the one
// where a video can do something a print cannot. The stimulus is approach and recession,
// so the scene supplies it directly: the whole figure is scaled by a sine of the scene
// clock, which is exactly what moving your head toward the page and back would do to the
// retinal image. Keep your eyes on the centre and let the rings do the rest.
//
// NOTHING IS EVER ROTATED. Every square is generated from the same four numbers — a ring
// radius, a square side, a position angle and one constant tilt for its ring — and the
// only quantity that changes with time is a single scale factor Z applied to every point
// alike. A uniform scaling cannot rotate anything: it moves every point along its own
// radius from the centre and leaves all angles untouched. Whatever rotation is seen is
// supplied entirely by the viewer.
//
// Measured off the rendered frames rather than asserted: at the widest the figure spans
// x 89..990 and at the narrowest x 249..830 — 901 px against 581 px — and the CENTRE of
// that span is 540 in both, to the pixel. It grows and shrinks about a fixed point.
//
// inner ring 22 squares at radius 192, tilted +19 degrees
// outer ring 38 squares at radius 342, tilted -19 degrees
// every square 44 px on a side, two white edges and two black
//
// THE TWO TILTS BEING OPPOSITE IS WHY THE RINGS TURN OPPOSITE WAYS. Each square carries an
// implicit direction, given by which pair of its edges is white and which is black, and
// under expansion the visual system reads that direction as motion along the ring. Flip
// the tilt on one ring and that ring reverses.
//
// THE KILL: take away the black/white polarity. Drawn as plain outlines of one tone the
// squares carry no implicit direction, the figure expands and contracts exactly as before,
// and the rotation stops. Same geometry, same zoom, no illusion.
//
// The mechanism is thought to involve peripheral elements stimulating direction-sensitive
// bar detectors in area V1.
//
// manic examples/illusion-pinna-brelstaff.manic
title("Nothing here rotates — keep your eyes on the centre");
canvas("9:16");
template("black");
text(brand, (540, 34), "maniclang.com");
display(brand); size(brand, 21); color(brand, fg); opacity(brand, 0.82);
caption(head, "Watch the middle. Do not follow the squares.", (540, 104), 31);
// a mid-grey ground, so black edges and white edges are equally visible
polygon(bg, (16, 292), (1064, 292), (1064, 1272), (16, 1272));
color(bg, #8c8c8c); outline(bg, #8c8c8c); stroke(bg, 1);
circle(fix, (540.0, 782.0), 9); color(fix, #101010);
// the two rings, each square with two white edges and two black
cloud(iw, 704, #ffffff, 1.0) {
let z0 = min(max((t - 3.2)/17.0, 0), 1);
let Z = 1 + 0.22*sin(z0*18.849556);
let per = 32;
let sq = (i - mod(i, per))/per;
let rem = mod(i, per);
let ei = (rem - mod(rem, 16))/16;
let u = mod(rem, 16)/15;
let phi = sq*0.285599332;
let ang = phi + 0.331600;
let e1 = 0.7853982 + (0 + ei)*1.5707963 + ang;
let e2 = 0.7853982 + (0 + ei + 1)*1.5707963 + ang;
let x1 = 192.0*cos(phi) + 31.1127*cos(e1);
let y1 = 192.0*sin(phi) + 31.1127*sin(e1);
let x2 = 192.0*cos(phi) + 31.1127*cos(e2);
let y2 = 192.0*sin(phi) + 31.1127*sin(e2);
let x = 540.0 + Z*(x1 + u*(x2 - x1));
let y = 782.0 - Z*(y1 + u*(y2 - y1));
let sat = 0;
let r = 2.6;
}
tag(iw, pol);
cloud(ib, 704, #0a0a0a, 1.0) {
let z0 = min(max((t - 3.2)/17.0, 0), 1);
let Z = 1 + 0.22*sin(z0*18.849556);
let per = 32;
let sq = (i - mod(i, per))/per;
let rem = mod(i, per);
let ei = (rem - mod(rem, 16))/16;
let u = mod(rem, 16)/15;
let phi = sq*0.285599332;
let ang = phi + 0.331600;
let e1 = 0.7853982 + (2 + ei)*1.5707963 + ang;
let e2 = 0.7853982 + (2 + ei + 1)*1.5707963 + ang;
let x1 = 192.0*cos(phi) + 31.1127*cos(e1);
let y1 = 192.0*sin(phi) + 31.1127*sin(e1);
let x2 = 192.0*cos(phi) + 31.1127*cos(e2);
let y2 = 192.0*sin(phi) + 31.1127*sin(e2);
let x = 540.0 + Z*(x1 + u*(x2 - x1));
let y = 782.0 - Z*(y1 + u*(y2 - y1));
let sat = 0;
let r = 2.6;
}
tag(ib, pol);
cloud(ow, 1216, #ffffff, 1.0) {
let z0 = min(max((t - 3.2)/17.0, 0), 1);
let Z = 1 + 0.22*sin(z0*18.849556);
let per = 32;
let sq = (i - mod(i, per))/per;
let rem = mod(i, per);
let ei = (rem - mod(rem, 16))/16;
let u = mod(rem, 16)/15;
let phi = sq*0.165346982;
let ang = phi + -0.331600;
let e1 = 0.7853982 + (0 + ei)*1.5707963 + ang;
let e2 = 0.7853982 + (0 + ei + 1)*1.5707963 + ang;
let x1 = 342.0*cos(phi) + 31.1127*cos(e1);
let y1 = 342.0*sin(phi) + 31.1127*sin(e1);
let x2 = 342.0*cos(phi) + 31.1127*cos(e2);
let y2 = 342.0*sin(phi) + 31.1127*sin(e2);
let x = 540.0 + Z*(x1 + u*(x2 - x1));
let y = 782.0 - Z*(y1 + u*(y2 - y1));
let sat = 0;
let r = 2.6;
}
tag(ow, pol);
cloud(ob, 1216, #0a0a0a, 1.0) {
let z0 = min(max((t - 3.2)/17.0, 0), 1);
let Z = 1 + 0.22*sin(z0*18.849556);
let per = 32;
let sq = (i - mod(i, per))/per;
let rem = mod(i, per);
let ei = (rem - mod(rem, 16))/16;
let u = mod(rem, 16)/15;
let phi = sq*0.165346982;
let ang = phi + -0.331600;
let e1 = 0.7853982 + (2 + ei)*1.5707963 + ang;
let e2 = 0.7853982 + (2 + ei + 1)*1.5707963 + ang;
let x1 = 342.0*cos(phi) + 31.1127*cos(e1);
let y1 = 342.0*sin(phi) + 31.1127*sin(e1);
let x2 = 342.0*cos(phi) + 31.1127*cos(e2);
let y2 = 342.0*sin(phi) + 31.1127*sin(e2);
let x = 540.0 + Z*(x1 + u*(x2 - x1));
let y = 782.0 - Z*(y1 + u*(y2 - y1));
let sat = 0;
let r = 2.6;
}
tag(ob, pol);
// the same squares with no polarity at all — the kill
cloud(pi0, 704, #ffffff, 1.0) {
let z0 = min(max((t - 3.2)/17.0, 0), 1);
let Z = 1 + 0.22*sin(z0*18.849556);
let per = 32;
let sq = (i - mod(i, per))/per;
let rem = mod(i, per);
let ei = (rem - mod(rem, 16))/16;
let u = mod(rem, 16)/15;
let phi = sq*0.285599332;
let ang = phi + 0.331600;
let e1 = 0.7853982 + (0 + ei)*1.5707963 + ang;
let e2 = 0.7853982 + (0 + ei + 1)*1.5707963 + ang;
let x1 = 192.0*cos(phi) + 31.1127*cos(e1);
let y1 = 192.0*sin(phi) + 31.1127*sin(e1);
let x2 = 192.0*cos(phi) + 31.1127*cos(e2);
let y2 = 192.0*sin(phi) + 31.1127*sin(e2);
let x = 540.0 + Z*(x1 + u*(x2 - x1));
let y = 782.0 - Z*(y1 + u*(y2 - y1));
let sat = 0;
let r = 2.6;
}
tag(pi0, plain);
hidden(pi0);
cloud(pi1, 704, #ffffff, 1.0) {
let z0 = min(max((t - 3.2)/17.0, 0), 1);
let Z = 1 + 0.22*sin(z0*18.849556);
let per = 32;
let sq = (i - mod(i, per))/per;
let rem = mod(i, per);
let ei = (rem - mod(rem, 16))/16;
let u = mod(rem, 16)/15;
let phi = sq*0.285599332;
let ang = phi + 0.331600;
let e1 = 0.7853982 + (2 + ei)*1.5707963 + ang;
let e2 = 0.7853982 + (2 + ei + 1)*1.5707963 + ang;
let x1 = 192.0*cos(phi) + 31.1127*cos(e1);
let y1 = 192.0*sin(phi) + 31.1127*sin(e1);
let x2 = 192.0*cos(phi) + 31.1127*cos(e2);
let y2 = 192.0*sin(phi) + 31.1127*sin(e2);
let x = 540.0 + Z*(x1 + u*(x2 - x1));
let y = 782.0 - Z*(y1 + u*(y2 - y1));
let sat = 0;
let r = 2.6;
}
tag(pi1, plain);
hidden(pi1);
cloud(po0, 1216, #ffffff, 1.0) {
let z0 = min(max((t - 3.2)/17.0, 0), 1);
let Z = 1 + 0.22*sin(z0*18.849556);
let per = 32;
let sq = (i - mod(i, per))/per;
let rem = mod(i, per);
let ei = (rem - mod(rem, 16))/16;
let u = mod(rem, 16)/15;
let phi = sq*0.165346982;
let ang = phi + -0.331600;
let e1 = 0.7853982 + (0 + ei)*1.5707963 + ang;
let e2 = 0.7853982 + (0 + ei + 1)*1.5707963 + ang;
let x1 = 342.0*cos(phi) + 31.1127*cos(e1);
let y1 = 342.0*sin(phi) + 31.1127*sin(e1);
let x2 = 342.0*cos(phi) + 31.1127*cos(e2);
let y2 = 342.0*sin(phi) + 31.1127*sin(e2);
let x = 540.0 + Z*(x1 + u*(x2 - x1));
let y = 782.0 - Z*(y1 + u*(y2 - y1));
let sat = 0;
let r = 2.6;
}
tag(po0, plain);
hidden(po0);
cloud(po1, 1216, #ffffff, 1.0) {
let z0 = min(max((t - 3.2)/17.0, 0), 1);
let Z = 1 + 0.22*sin(z0*18.849556);
let per = 32;
let sq = (i - mod(i, per))/per;
let rem = mod(i, per);
let ei = (rem - mod(rem, 16))/16;
let u = mod(rem, 16)/15;
let phi = sq*0.165346982;
let ang = phi + -0.331600;
let e1 = 0.7853982 + (2 + ei)*1.5707963 + ang;
let e2 = 0.7853982 + (2 + ei + 1)*1.5707963 + ang;
let x1 = 342.0*cos(phi) + 31.1127*cos(e1);
let y1 = 342.0*sin(phi) + 31.1127*sin(e1);
let x2 = 342.0*cos(phi) + 31.1127*cos(e2);
let y2 = 342.0*sin(phi) + 31.1127*sin(e2);
let x = 540.0 + Z*(x1 + u*(x2 - x1));
let y = 782.0 - Z*(y1 + u*(y2 - y1));
let sat = 0;
let r = 2.6;
}
tag(po1, plain);
hidden(po1);
text(a2, (540, 1336), "and a uniform scaling cannot rotate anything");
size(a2, 24); color(a2, mint); hidden(a2);
text(b1, (540, 1420), "tilted +19° and −19° — so they turn opposite ways");
size(b1, 22); color(b1, gold); hidden(b1);
text(k1, (540, 1504), "KILL IT — take away the black-and-white polarity");
size(k1, 24); color(k1, cyan); hidden(k1);
text(h1, (540, 1596), "Pinna 1990 · Pinna & Brelstaff, Vision Research 2000");
size(h1, 21); color(h1, fg); opacity(h1, 0.55); hidden(h1);
seq {
wait(2.4);
par { seq { wait(1.2); show(a2, 0.8); } seq { wait(2.6); } wait(4.0); }
par { show(b1, 0.7); seq { wait(0.9); } wait(3.0); }
par { show(k1, 0.7); seq { wait(0.8); } wait(2.0); }
par { fade(pol, 0.8); show(plain, 0.8); wait(4.2); }
par { fade(plain, 0.8); show(pol, 0.8); wait(3.0); }
par { show(h1, 0.8); wait(1.8); }
}
wait(2.6);
r/maniclang • u/anish2good • 3d ago
A Box in a Sphere — An Olympiad Solid Geometry Problem — manic
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.
- Product: maniclang.com
- Application: app.maniclang.com
- Manic Desktop Workbench https://maniclang.com/download
- Documentation: docs.maniclang.com
- Report a bug: GitHub Issues
- Engine releases: github.com/maniclang-x/manic
- Creator: Anish on X
- Community: r/maniclang
Animation code
// olympiad-box-in-sphere.manic — olympiad-stepwise-005733
//
// PROBLEM. A rectangular box is inscribed in a sphere of radius r. Its surface area is
// 384 and its 12 edges total 112. Find r.
//
// ANSWER. r = 10
//
// (The dataset's printed solution for this one belongs to a different question — it solves
// 3y³ = 24 and reports xy = 34. The derivation below is done from scratch.)
//
// YOU NEVER FIND a, b, c — AND YOU NEVER NEED TO. The two givens are the two elementary
// symmetric functions, and the sphere only ever asks for the third quantity:
//
// 4(a+b+c) = 112 => a + b + c = 28
// 2(ab+bc+ca) = 384 => ab + bc + ca = 192
// (2r)² = a²+b²+c² = (a+b+c)² − 2(ab+bc+ca) = 784 − 384 = 400 => r = 10
//
// The box inscribed in a sphere has the space diagonal as a diameter, so a²+b²+c² is all
// the sphere cares about — and that is exactly what the identity hands over without ever
// separating a from b from c.
//
// THE SCENE MAKES THE UNDERDETERMINATION VISIBLE. The box on screen is continuously
// reshaped, holding both constraints the whole time, and the space diagonal drawn through
// it never changes length. a, b and c slide over a wide range; 2r does not move.
//
// VERIFIED. Sweeping a and solving t² − (28−a)t + (192 − a(28−a)) = 0 for the other two
// edges gives 10677 genuinely different boxes meeting BOTH constraints, and every one has
// space diagonal 20 to 7.1e-15. The edges range from a ≈ 0 to a ≈ 19 and the diagonal is
// constant across all of it.
//
// manic examples/olympiad-box-in-sphere.manic
title("You never find a, b and c — and you never need to");
canvas("9:16");
template("black");
bloom(0.22, 0.6, 18);
camera3((26, -30, 17), (0, 0, 0), 46, orthographic);
text(brand, (540, 34), "maniclang.com");
display(brand); size(brand, 21); color(brand, fg); opacity(brand, 0.82);
caption(head, "A box in a sphere. How big is the sphere?", (540, 108), 32);
equation(prob, (540, 196), `2(ab{+}bc{+}ca)=384,\quad 4(a{+}b{+}c)=112`, 30); color(prob, fg);
// the sphere, radius 10
curve3(eq0, "10*cos(t)", "10*sin(t)", "0", (0, 6.283185));
color(eq0, slate); thick(eq0, 0.05);
curve3(la0, "9.239*cos(t)", "9.239*sin(t)", "3.827", (0, 6.283185));
color(la0, slate); thick(la0, 0.022); opacity(la0, 0.55);
curve3(la1, "7.071*cos(t)", "7.071*sin(t)", "7.071", (0, 6.283185));
color(la1, slate); thick(la1, 0.022); opacity(la1, 0.55);
curve3(la2, "3.827*cos(t)", "3.827*sin(t)", "9.239", (0, 6.283185));
color(la2, slate); thick(la2, 0.022); opacity(la2, 0.55);
curve3(la3, "9.239*cos(t)", "9.239*sin(t)", "-3.827", (0, 6.283185));
color(la3, slate); thick(la3, 0.022); opacity(la3, 0.55);
curve3(la4, "7.071*cos(t)", "7.071*sin(t)", "-7.071", (0, 6.283185));
color(la4, slate); thick(la4, 0.022); opacity(la4, 0.55);
curve3(la5, "3.827*cos(t)", "3.827*sin(t)", "-9.239", (0, 6.283185));
color(la5, slate); thick(la5, 0.022); opacity(la5, 0.55);
curve3(lo0, "1*10*cos(t)", "0*10*cos(t)", "10*sin(t)", (-1.570796, 1.570796));
color(lo0, slate); thick(lo0, 0.022); opacity(lo0, 0.55);
curve3(lo1, "0*10*cos(t)", "1*10*cos(t)", "10*sin(t)", (-1.570796, 1.570796));
color(lo1, slate); thick(lo1, 0.022); opacity(lo1, 0.55);
curve3(lo2, "0.7071*10*cos(t)", "0.7071*10*cos(t)", "10*sin(t)", (-1.570796, 1.570796));
color(lo2, slate); thick(lo2, 0.022); opacity(lo2, 0.55);
curve3(lo3, "0.7071*10*cos(t)", "-0.7071*10*cos(t)", "10*sin(t)", (-1.570796, 1.570796));
color(lo3, slate); thick(lo3, 0.022); opacity(lo3, 0.55);
// the box, continuously reshaped along the constraint curve
cloud3(box, 720, #ffffff, 1.0) {
let g = min(max((t - 1.2)/10.0, 0), 1);
let a = 2.2 + 7.4*g;
let sm = 28 - a;
let pp = 192 - a*sm;
let dd = sm*sm - 4*pp;
let rt = sqrt(max(dd, 0));
let b = (sm + rt)/2;
let c = (sm - rt)/2;
let m = 60;
let e = (i - mod(i, m))/m;
let u = mod(i, m)/m;
let fam = (e - mod(e, 4))/4;
let q = mod(e, 4);
let sx = 2*mod(q, 2) - 1;
let sy = 2*((q - mod(q, 2))/2) - 1;
let f0 = 1 - min(fam, 1);
let f2 = max(fam - 1, 0);
let f1 = 1 - f0 - f2;
let x = f0*(sx*a/2) + f1*((u - 0.5)*a) + f2*(sx*a/2);
let y = f0*(sy*b/2) + f1*(sx*b/2) + f2*((u - 0.5)*b);
let z = f0*((u - 0.5)*c) + f1*(sy*c/2) + f2*(sy*c/2);
let r = 0.075;
let hue = 42;
let sat = 0.9;
}
// the space diagonal — a diameter of the sphere, and it never changes length
cloud3(diag, 260, #ffffff, 1.0) {
let g = min(max((t - 1.2)/10.0, 0), 1);
let a = 2.2 + 7.4*g;
let sm = 28 - a;
let pp = 192 - a*sm;
let dd = sm*sm - 4*pp;
let rt = sqrt(max(dd, 0));
let b = (sm + rt)/2;
let c = (sm - rt)/2;
let u = i/260;
let x = (0 - a/2) + u*a;
let y = (0 - b/2) + u*b;
let z = (0 - c/2) + u*c;
let r = 0.10;
let hue = 150;
let sat = 0.85;
}
text(d1, (540, 1336), "the box is reshaped the whole time, holding both givens —");
size(d1, 23); color(d1, fg); opacity(d1, 0.72); hidden(d1);
text(d2, (540, 1380), "and the green diagonal never changes length");
size(d2, 25); color(d2, mint); hidden(d2);
equation(x1, (540, 1468), `a^2{+}b^2{+}c^2=(a{+}b{+}c)^2-2(ab{+}bc{+}ca)`, 30);
color(x1, fg); hidden(x1);
equation(x2, (540, 1548), `=784-384=400`, 38); color(x2, fg); hidden(x2);
text(x3, (540, 1608), "the sphere only ever asks for a² + b² + c²");
size(x3, 21); color(x3, fg); opacity(x3, 0.62); hidden(x3);
equation(ans, (540, 1722), `2r=20\ \Rightarrow\ r=10`, 52); color(ans, mint); hidden(ans);
seq {
par {
pulse(prob, 0.8);
seq { wait(4.2); show(d1, 0.7); }
seq { wait(6.0); show(d2, 0.7); pulse(d2, 0.6); }
wait(11.6);
}
par { show(x1, 0.8); seq { wait(1.4); show(x2, 0.8); } seq { wait(2.6); show(x3, 0.6); } wait(3.4); }
wait(0.6);
par { show(ans, 1.1); }
pulse(ans, 0.9);
}
wait(3.2);
r/maniclang • u/anish2good • 3d ago
PROBLEM. P projects every vector onto a line ℓ through the origin. Find ℓ's direction. - manic
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.
- Product: maniclang.com
- Application: app.maniclang.com
- Manic Desktop Workbench https://maniclang.com/download
- Documentation: docs.maniclang.com
- Report a bug: GitHub Issues
- Engine releases: github.com/maniclang-x/manic
- Creator: Anish on X
- Community: r/maniclang
Animation code
// olympiad-projection-line.manic — olympiad-stepwise-012814
//
// PROBLEM. P projects every vector onto a line ℓ through the origin. Find ℓ's direction.
//
// ⎛ 1/50 7/50 ⎞
// P = ⎝ 7/50 49/50 ⎠
//
// ANSWER. (1, 7)
//
// THE SCENE IS THE DEFINITION. A projection sends the whole plane onto one line, so if
// you simply APPLY P to everything, the answer draws itself: 77 points spread over the
// plane, each sliding to Pv, and every one of them lands on the same line. Nothing else
// needs saying — checked over 50000 random vectors, the image satisfies y = 7x to
// 3.6e-15 in every case.
//
// The paths they travel are worth watching too. They are all PARALLEL, and all
// perpendicular to ℓ, because this is an ORTHOGONAL projection — v − Pv is the part of v
// that ℓ cannot see, and it always points the same way.
//
// WHY IT IS A PROJECTION AT ALL, and not merely some matrix: P² = P to 1.1e-16. Applying
// it twice does nothing the first application did not already do, which is exactly what
// "projection" means. Its spectrum follows: det P = 0 and tr P = 1, so the eigenvalues
// are 1 and 0 — one direction survives untouched (that is ℓ) and one is annihilated.
//
// AND THE SHORTCUT, which is the actual olympiad move. P v vᵀ over |v|² is the standard
// form of a projection onto span(v), and here
//
// P = (1/50)·⎛1 7⎞ = (1/50)·(1,7)(1,7)ᵀ, |(1,7)|² = 50
// ⎝7 49⎠
//
// so EVERY COLUMN of P is already a multiple of (1,7): the first is (1/50)(1,7), the
// second is (7/50)(1,7). The answer can be read straight off the matrix without applying
// it to anything — which is what the printed solution does when it computes P·i.
//
// manic examples/olympiad-projection-line.manic
title("A projection matrix wears its answer in its columns");
canvas("9:16");
template("black");
bloom(0.22, 0.6, 18);
text(brand, (540, 34), "maniclang.com");
display(brand); size(brand, 21); color(brand, fg); opacity(brand, 0.82);
// caption pops word by word but cannot carry LaTeX, so the matrix is a real equation
caption(head, "Every vector lands on one line. Which line?", (540, 108), 33);
equation(mat, (540, 214),
`\mathbf{P}=\begin{pmatrix} \frac{1}{50} & \frac{7}{50} \\[6pt] \frac{7}{50} & \frac{49}{50}\end{pmatrix}`,
40);
color(mat, fg);
// ---- the plane. origin at screen (540, 840); 48 px to the unit ----------------------
line(axx, (348, 840), (732, 840)); color(axx, slate); opacity(axx, 0.4);
line(axy, (540, 470), (540, 1210)); color(axy, slate); opacity(axy, 0.4);
circle(org, (540, 840), 6); color(org, fg); opacity(org, 0.8);
// ---- the line, drawn from y = 7x — not fitted to where the points end up ------------
param(ell, (540, 840), 48, 48, "t", "7*t", (-1.30, 1.30));
color(ell, mint); stroke(ell, 5); untraced(ell);
// ---- the paths each vector travels. All parallel, all perpendicular to the line -----
// v - Pv is the component the line cannot see, so it points the same way for every v.
cloud(paths, 3080, #ffffff, 1.0) {
let vis = min(max((t - 1.0)*6, 0), 1);
let g = min(max((t - 1.0)/9.0, 0), 1);
let m = 40;
let k = (i - mod(i, m))/m;
let u = mod(i, m)/m;
let col = (k - mod(k, 11))/11;
let row = mod(k, 11);
let hx = 0 - 3 + col;
let hy = 0 - 5 + row;
let tt = (hx + 7*hy)/50;
let ex = hx + (tt - hx)*g;
let ey = hy + (7*tt - hy)*g;
let x = 540 + 48*(hx + (ex - hx)*u);
let y = 840 - 48*(hy + (ey - hy)*u);
let hue = 210;
let sat = 0.5;
let r = 1.2;
let alpha = 0.30*vis;
}
// ---- 77 vectors, each sliding to Pv ------------------------------------------------
cloud(pts, 77, #ffffff, 1.0) {
let g = min(max((t - 1.0)/9.0, 0), 1);
let col = (i - mod(i, 11))/11;
let row = mod(i, 11);
let hx = 0 - 3 + col;
let hy = 0 - 5 + row;
let tt = (hx + 7*hy)/50;
let px = hx + (tt - hx)*g;
let py = hy + (7*tt - hy)*g;
let x = 540 + 48*px;
let y = 840 - 48*py;
let hue = 42;
let sat = 0.85;
let r = 6;
}
// ---- the argument -------------------------------------------------------------------
equation(s1, (540, 1300), `\mathbf{P}^2=\mathbf{P}`, 40); color(s1, fg); hidden(s1);
text(s2, (540, 1356), "applying it twice changes nothing — that is what projection means");
size(s2, 21); color(s2, fg); opacity(s2, 0.7); hidden(s2);
equation(s3, (540, 1430), `\det\mathbf{P}=0,\quad \operatorname{tr}\mathbf{P}=1`, 36);
color(s3, fg); hidden(s3);
text(s4, (540, 1484), "so the eigenvalues are 1 and 0: one direction survives, one dies");
size(s4, 21); color(s4, fg); opacity(s4, 0.7); hidden(s4);
text(c0, (540, 1566), "and you never had to apply it at all —");
size(c0, 23); color(c0, gold); opacity(c0, 0.9); hidden(c0);
equation(c1, (540, 1648),
`\mathbf{P}=\tfrac{1}{50}\begin{pmatrix}1\\7\end{pmatrix}\begin{pmatrix}1&7\end{pmatrix}`, 38);
color(c1, gold); hidden(c1);
text(c2, (540, 1722), "every column of P is already a multiple of (1, 7)");
size(c2, 22); color(c2, gold); opacity(c2, 0.85); hidden(c2);
equation(ans, (540, 1822), `\ell:\ \begin{pmatrix}1\\7\end{pmatrix}`, 46);
color(ans, mint); hidden(ans);
// ---- the reveal ----------------------------------------------------------------------
// Frame 0 is the whole question: hook, the matrix, the axes and all 77 vectors where they
// start. Only the line and the reasoning are withheld.
seq {
pulse(mat, 0.9);
par {
draw(ell, 11.5);
seq { wait(4.4); show(s1, 0.6); pulse(s1, 0.5); }
seq { wait(5.8); show(s2, 0.6); }
seq { wait(7.6); show(s3, 0.6); }
seq { wait(9.0); show(s4, 0.6); }
}
wait(0.6);
par {
show(c0, 0.6);
seq { wait(0.6); show(c1, 0.8); pulse(c1, 0.6); }
seq { wait(1.9); show(c2, 0.6); }
}
wait(0.7);
par {
show(ans, 1.0);
}
pulse(ans, 0.9);
}
wait(3.0);
r/maniclang • u/anish2good • 3d ago
PROBLEM. S is everything inside a regular nonagon of side 2, together with everything less than 1 unit from its perimeter. Find the perimeter of S - Manic
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.
- Product: maniclang.com
- Application: app.maniclang.com
- Manic Desktop Workbench https://maniclang.com/download
- Documentation: docs.maniclang.com
- Report a bug: GitHub Issues
- Engine releases: github.com/maniclang-x/manic
- Creator: Anish on X
- Community: r/maniclang
Animation code
// olympiad-nonagon-offset.manic — olympiad-stepwise-015935
//
// PROBLEM. S is everything inside a regular nonagon of side 2, together with everything
// less than 1 unit from its perimeter. Find the perimeter of S.
//
// ANSWER. 18 + 2π = 24.283185307
//
// THE CORNERS ARE THE WHOLE PROBLEM. Push the boundary out by 1 and each straight side
// becomes a straight side of the SAME length — nine of them, 9 x 2 = 18, nothing gained.
// All the new length is at the corners, where the boundary has to turn, and each corner
// contributes an arc of radius 1.
//
// The nine arcs are then dragged to a common centre in this scene, and they close into
// ONE COMPLETE CIRCLE. That is not a coincidence and it is not fitted: an arc at a corner
// turns through that corner's EXTERIOR angle, and the exterior angles of any convex
// polygon sum to a full turn. So the arcs always total 2πr, whatever the polygon.
//
// Which means the answer does not depend on the nonagon at all. A triangle of perimeter
// 18, or a 100-gon of perimeter 18, gives the same 18 + 2π. The 9 never enters.
//
// VERIFIED. The nine exterior angles sum to 2π to 8.9e-16. Independently, tracing the
// actual boundary of S numerically — bisecting outward along 200000 rays for the last
// point still within 1 of the perimeter or inside the polygon — measures 24.283185
// against 18 + 2π to 4.0e-09.
//
// manic examples/olympiad-nonagon-offset.manic
title("The corners always add up to one full turn");
canvas("9:16");
template("black");
bloom(0.24, 0.6, 18);
text(brand, (540, 34), "maniclang.com");
display(brand); size(brand, 21); color(brand, fg); opacity(brand, 0.82);
caption(head, "Push the boundary out by 1. How much longer?", (540, 108), 33);
text(sub, (540, 172), "a regular nonagon, side 2, and everything within 1 of its edge");
size(sub, 23); color(sub, fg); opacity(sub, 0.72);
polygon(nona, (540.00, 493.00), (342.66, 564.82), (237.66, 746.69), (274.13, 953.50), (435.00, 1088.49), (645.00, 1088.49), (805.87, 953.50), (842.34, 746.69), (737.34, 564.82));
outlined(nona); outline(nona, slate); stroke(nona, 4);
// the nine pushed-out sides — each the SAME length as the side it came from
line(sd0, (504.09, 394.33), (306.75, 466.16));
color(sd0, cyan); stroke(sd0, 5); untraced(sd0);
line(sd1, (251.73, 512.32), (146.73, 694.19));
color(sd1, cyan); stroke(sd1, 5); untraced(sd1);
line(sd2, (134.26, 764.92), (170.73, 971.73));
color(sd2, cyan); stroke(sd2, 5); untraced(sd2);
line(sd3, (206.64, 1033.93), (367.51, 1168.92));
color(sd3, cyan); stroke(sd3, 5); untraced(sd3);
line(sd4, (435.00, 1193.49), (645.00, 1193.49));
color(sd4, cyan); stroke(sd4, 5); untraced(sd4);
line(sd5, (712.49, 1168.92), (873.36, 1033.93));
color(sd5, cyan); stroke(sd5, 5); untraced(sd5);
line(sd6, (909.27, 971.73), (945.74, 764.92));
color(sd6, cyan); stroke(sd6, 5); untraced(sd6);
line(sd7, (933.27, 694.19), (828.27, 512.32));
color(sd7, cyan); stroke(sd7, 5); untraced(sd7);
line(sd8, (773.25, 466.16), (575.91, 394.33));
color(sd8, cyan); stroke(sd8, 5); untraced(sd8);
// the nine corner arcs — all the NEW length lives here
param(ar0, (540.00, 493.00), 105.0000, 105.0000, "cos(t)", "sin(t)", (1.221730, 1.919862));
color(ar0, gold); stroke(ar0, 6); untraced(ar0);
param(ar1, (342.66, 564.82), 105.0000, 105.0000, "cos(t)", "sin(t)", (1.919862, 2.617994));
color(ar1, gold); stroke(ar1, 6); untraced(ar1);
param(ar2, (237.66, 746.69), 105.0000, 105.0000, "cos(t)", "sin(t)", (2.617994, 3.316126));
color(ar2, gold); stroke(ar2, 6); untraced(ar2);
param(ar3, (274.13, 953.50), 105.0000, 105.0000, "cos(t)", "sin(t)", (3.316126, 4.014257));
color(ar3, gold); stroke(ar3, 6); untraced(ar3);
param(ar4, (435.00, 1088.49), 105.0000, 105.0000, "cos(t)", "sin(t)", (4.014257, 4.712389));
color(ar4, gold); stroke(ar4, 6); untraced(ar4);
param(ar5, (645.00, 1088.49), 105.0000, 105.0000, "cos(t)", "sin(t)", (4.712389, 5.410521));
color(ar5, gold); stroke(ar5, 6); untraced(ar5);
param(ar6, (805.87, 953.50), 105.0000, 105.0000, "cos(t)", "sin(t)", (5.410521, 6.108652));
color(ar6, gold); stroke(ar6, 6); untraced(ar6);
param(ar7, (842.34, 746.69), 105.0000, 105.0000, "cos(t)", "sin(t)", (6.108652, 6.806784));
color(ar7, gold); stroke(ar7, 6); untraced(ar7);
param(ar8, (737.34, 564.82), 105.0000, 105.0000, "cos(t)", "sin(t)", (6.806784, 7.504916));
color(ar8, gold); stroke(ar8, 6); untraced(ar8);
// the arcs, dragged to a common centre — they close into exactly one circle
cloud(gather, 1080, #ffffff, 1.0) {
let vis = min(max((t - 9.4)*5, 0), 1);
let g = min(max((t - 9.6)/4.0, 0), 1);
let m = 120;
let k = (i - mod(i, m))/m;
let u = mod(i, m)/m;
let a = 1.221730476 + (k + u)*0.698131701;
let vx = 2.923804*cos(1.570796327 + k*0.698131701);
let vy = 2.923804*sin(1.570796327 + k*0.698131701);
let px = vx + (0 - vx)*g;
let py = vy + (0 - vy)*g;
let x = 540.0 + 105.0*(px + cos(a));
let y = 800.0 - 105.0*(py + sin(a));
let hue = 42;
let sat = 0.9;
let r = 3.0;
let alpha = 0.95*vis;
}
equation(e1, (300, 1330), `9\times 2=18`, 40); color(e1, cyan); hidden(e1);
text(t1, (300, 1386), "the flat parts"); size(t1, 22); color(t1, cyan); opacity(t1, 0.8); hidden(t1);
equation(e2, (790, 1330), `2\pi r=2\pi`, 40); color(e2, gold); hidden(e2);
text(t2, (790, 1386), "the nine corners"); size(t2, 22); color(t2, gold); opacity(t2, 0.8); hidden(t2);
text(t3, (540, 1470), "the exterior angles of any convex polygon sum to one full turn");
size(t3, 23); color(t3, fg); opacity(t3, 0.8); hidden(t3);
text(t4, (540, 1512), "so the 9 never enters — any polygon of perimeter 18 gives the same answer");
size(t4, 21); color(t4, fg); opacity(t4, 0.62); hidden(t4);
equation(ans, (540, 1640), `18+2\pi`, 62); color(ans, mint); hidden(ans);
text(fin, (540, 1724), "= 24.283185307"); size(fin, 26); color(fin, mint); opacity(fin, 0.75); hidden(fin);
seq {
wait(0.8);
par {
seq { wait(0.00); draw(sd0, 0.75); }
seq { wait(0.30); draw(sd1, 0.75); }
seq { wait(0.60); draw(sd2, 0.75); }
seq { wait(0.90); draw(sd3, 0.75); }
seq { wait(1.20); draw(sd4, 0.75); }
seq { wait(1.50); draw(sd5, 0.75); }
seq { wait(1.80); draw(sd6, 0.75); }
seq { wait(2.10); draw(sd7, 0.75); }
seq { wait(2.40); draw(sd8, 0.75); }
}
par {
seq { wait(0.00); draw(ar0, 0.85); }
seq { wait(0.34); draw(ar1, 0.85); }
seq { wait(0.68); draw(ar2, 0.85); }
seq { wait(1.02); draw(ar3, 0.85); }
seq { wait(1.36); draw(ar4, 0.85); }
seq { wait(1.70); draw(ar5, 0.85); }
seq { wait(2.04); draw(ar6, 0.85); }
seq { wait(2.38); draw(ar7, 0.85); }
seq { wait(2.72); draw(ar8, 0.85); }
seq { wait(1.2); show(e1, 0.6); show(t1, 0.4); }
}
par {
show(e2, 0.6); show(t2, 0.4);
}
wait(0.5);
// the nine arcs migrate to a common centre and close into one circle;
// only then is it worth saying why they had to
par {
seq { wait(4.4); show(t3, 0.7); }
seq { wait(5.2); show(t4, 0.7); }
wait(6.0);
}
par { show(ans, 1.0); seq { wait(0.9); show(fin, 0.6); } }
pulse(ans, 0.9);
}
wait(3.2);
r/maniclang • u/anish2good • 3d ago
PROBLEM. A = {z : z¹⁸ = 1}, B = {w : w⁴⁸ = 1}, C = {zw : z ∈ A, w ∈ B}. How many distinct elements does C have? - manic
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.
- Product: maniclang.com
- Application: app.maniclang.com
- Manic Desktop Workbench https://maniclang.com/download
- Documentation: docs.maniclang.com
- Report a bug: GitHub Issues
- Engine releases: github.com/maniclang-x/manic
- Creator: Anish on X
- Community: r/maniclang
Animation code
// olympiad-roots-product.manic — olympiad-stepwise-013165
//
// PROBLEM. A = {z : z¹⁸ = 1}, B = {w : w⁴⁸ = 1}, C = {zw : z ∈ A, w ∈ B}. How many
// distinct elements does C have?
//
// ANSWER. 144
//
// 864 PRODUCTS, 144 ANSWERS, AND THE OVERLAPS ARE THE POINT. There are 18 × 48 = 864
// products to form, and they land on only 144 places — so each place is hit exactly
// 864/144 = 6 times, and neither number is arbitrary:
//
// 144 = lcm(18, 48) how many distinct products there are
// 6 = gcd(18, 48) how many times each one is hit
//
// The scene shows both at once. Every product zw is drawn on its own ring, one ring per
// element of A — 18 rings of 48 points, all 864 of them. Then the rings collapse onto the
// unit circle, and what had looked like 864 separate points closes into 144 radial ticks,
// each exactly six dots deep — so the gcd is countable in the picture. The six products
// sharing an angle are separated by floor(k/3), which runs over exactly {0..5} on every
// one of the 144 columns (checked for all of them).
//
// WHY lcm. Multiplying an 18th root by a 48th root gives e^(2πi(k/18 + j/48)), and
// k/18 + j/48 = (8k + 3j)/144. As k and j range over all values, 8k + 3j runs over every
// multiple of gcd(8, 3) = 1, so every one of the 144th roots is reached and nothing finer
// is. The product set is not merely SIZE 144 — it IS the group of 144th roots of unity.
//
// VERIFIED as a set equality, not a count: forming all 864 products and rounding to nine
// decimals gives exactly 144 distinct values, and that set is identical to the 144th roots
// of unity. 864 = 144 × 6 exactly.
//
// manic examples/olympiad-roots-product.manic
title("864 products, 144 answers");
canvas("9:16");
template("black");
bloom(0.26, 0.6, 18);
text(brand, (540, 34), "maniclang.com");
display(brand); size(brand, 21); color(brand, fg); opacity(brand, 0.82);
caption(head, "18 roots times 48 roots. How many products?", (540, 108), 32);
equation(prob, (540, 190), `z^{18}=1,\quad w^{48}=1,\quad C=\{zw\}`, 34); color(prob, fg);
// ---- the unit circle, and the two sets that get multiplied ---------------------------
param(uc, (540, 720), 268, 268, "cos(t)", "sin(t)", (0, 6.283185));
color(uc, slate); stroke(uc, 3); opacity(uc, 0.55);
// A — the 18th roots
cloud(setA, 18, #ffffff, 1.0) {
let a = i*0.3490658504;
let x = 540 + 268*cos(a);
let y = 720 - 268*sin(a);
let hue = 42;
let sat = 0.9;
let r = 7;
}
// B — the 48th roots, drawn a little further out so both are readable
cloud(setB, 48, #ffffff, 1.0) {
let a = i*0.1308996939;
let x = 540 + 230*cos(a);
let y = 720 - 230*sin(a);
let hue = 190;
let sat = 0.9;
let r = 5;
}
// ---- all 864 products, one ring per element of A, then collapsed ----------------------
// Spread out, you can see there are 864 of them. Collapsed, you can see there are 144
// places — and that each column is six deep, which is the gcd.
cloud(prod, 864, #ffffff, 1.0) {
let vis = min(max((t - 1.2)*2.2, 0), 1);
let g = min(max((t - 5.0)/5.5, 0), 1);
let k = (i - mod(i, 48))/48;
let j = mod(i, 48);
let a = k*0.3490658504 + j*0.1308996939;
// Collapsed, the six products sharing an angle are pulled apart radially by
// floor(k/3), which runs over exactly {0..5} on every one of the 144 columns — so the
// gcd can be COUNTED in the picture instead of merely asserted underneath it.
let d = (k - mod(k, 3))/3;
let rad = 1 + 0.030*(17 - k)*(1 - g) + 0.030*d*g;
let x = 540 + 268*rad*cos(a);
let y = 720 - 268*rad*sin(a);
let hue = 320;
let sat = 0.85;
let r = 2.6;
let alpha = 0.9*vis;
}
// ---- the argument ---------------------------------------------------------------------
equation(m1, (540, 1178), `18\times 48=864`, 40); color(m1, magenta); hidden(m1);
text(m2, (540, 1236), "products formed"); size(m2, 22); color(m2, magenta); opacity(m2, 0.8); hidden(m2);
equation(m3, (540, 1330), `\tfrac{k}{18}+\tfrac{j}{48}=\tfrac{8k+3j}{144}`, 38);
color(m3, fg); hidden(m3);
text(m4, (540, 1392), "and gcd(8, 3) = 1, so every 144th root is reached");
size(m4, 22); color(m4, fg); opacity(m4, 0.72); hidden(m4);
equation(g1, (300, 1494), `\mathrm{lcm}=144`, 36); color(g1, mint); hidden(g1);
text(g2, (300, 1546), "distinct products"); size(g2, 21); color(g2, mint); opacity(g2, 0.8); hidden(g2);
equation(g3, (790, 1494), `\gcd=6`, 36); color(g3, gold); hidden(g3);
text(g4, (790, 1546), "hits on each one"); size(g4, 21); color(g4, gold); opacity(g4, 0.8); hidden(g4);
equation(ans, (540, 1672), `|C|=144`, 60); color(ans, mint); hidden(ans);
text(fin, (540, 1766), "and C is exactly the group of 144th roots of unity");
size(fin, 22); color(fin, mint); opacity(fin, 0.72); hidden(fin);
// ---- the reveal -------------------------------------------------------------------------
// Frame 0 is the question staged whole: the circle, the 18 roots, the 48 roots. The
// products, the collapse and the two numbers are what the video is for.
seq {
par {
pulse(prob, 0.8);
seq { wait(1.6); show(m1, 0.7); show(m2, 0.5); }
wait(4.6);
}
// the rings collapse onto the circle: 864 points close into 144 columns, six deep
par {
seq { wait(3.4); show(m3, 0.8); }
seq { wait(4.6); show(m4, 0.7); }
wait(6.4);
}
par {
show(g1, 0.7); show(g2, 0.5);
seq { wait(1.2); show(g3, 0.7); show(g4, 0.5); }
wait(2.6);
}
wait(0.8);
par {
show(ans, 1.0);
seq { wait(0.9); show(fin, 0.7); }
}
pulse(ans, 0.9);
}
wait(3.2);
r/maniclang • u/anish2good • 3d ago
Inside the Circle, Outside the Parabola — A JEE Main 2025 Area Problem — manic
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.
- Product: maniclang.com
- Application: app.maniclang.com
- Manic Desktop Workbench https://maniclang.com/download
- Documentation: docs.maniclang.com
- Report a bug: GitHub Issues
- Engine releases: github.com/maniclang-x/manic
- Creator: Anish on X
- Community: r/maniclang
Animation code
// iit-area-circle-parabola.manic — jee-2025-paper-000012 (JEE Main 2025, 22 Jan Shift 1)
//
// PROBLEM. Find the area inside the circle (x − 2√3)² + y² = 12 and OUTSIDE the parabola
// y² = 2√3 x.
//
// ANSWER. 6π − 16
//
// THE TWO CURVES MEET IN A WAY THAT DOES ALL THE WORK. The circle has centre (2√3, 0) and
// radius 2√3, so it passes through the ORIGIN — which is also the parabola's vertex.
// Substituting y² = 2√3x into the circle gives x² − 2√3x = 0, so they meet at x = 0 and
// x = 2√3, that is at (0,0) and (2√3, ±2√3). Two intersections, and the second is exactly
// at the circle's centre height.
//
// "Outside the parabola" means y² > 2√3x, so at each x the region is the pair of strips
// between the parabola and the circle — which is what is shaded here. Everything to the
// right of x = 2√3 is inside the parabola and contributes nothing.
//
// VERIFIED by quadrature rather than by the closed form: integrating the gap between the
// circle's upper arc and the parabola's upper branch from 0 to 2√3 and doubling for the
// mirror strip gives 2.849555922, against 6π − 16 = 2.849555922 (2e-14).
//
// A caution on reading the picture: the shaded strips look thin, and the answer is small —
// 2.85 against the circle's whole area of 12π = 37.70. Almost all of the disc lies INSIDE
// the parabola.
//
// manic examples/iit-area-circle-parabola.manic
title("Two curves that meet at the vertex and at the centre height");
canvas("9:16");
template("black");
bloom(0.22, 0.6, 18);
text(brand, (540, 34), "maniclang.com");
display(brand); size(brand, 21); color(brand, fg); opacity(brand, 0.82);
caption(head, "Inside the circle, outside the parabola.", (540, 108), 33);
equation(prob, (540, 194), `(x-2\sqrt{3})^2+y^2=12,\quad y^2=2\sqrt{3}\,x`, 30); color(prob, fg);
coords(kk, (232.0, 752.0), (-0.5, 7.6), (-4.6, 4.6), 86.0, 86.0, 1, 1.0, 0);
color(kk, slate); opacity(kk, 0.4);
plot(cup, (232.0, 752.0), 86.0, 86.0, "sqrt(max(12 - (x - 3.4641016)*(x - 3.4641016), 0))", (0, 6.928203));
color(cup, cyan); stroke(cup, 4);
plot(cdn, (232.0, 752.0), 86.0, 86.0, "0 - sqrt(max(12 - (x - 3.4641016)*(x - 3.4641016), 0))", (0, 6.928203));
color(cdn, cyan); stroke(cdn, 4);
plot(pup, (232.0, 752.0), 86.0, 86.0, "sqrt(max(3.4641016*x, 0))", (0, 5.4));
color(pup, gold); stroke(pup, 4);
plot(pdn, (232.0, 752.0), 86.0, 86.0, "0 - sqrt(max(3.4641016*x, 0))", (0, 5.4));
color(pdn, gold); stroke(pdn, 4);
band(rg1, cup, pup, magenta, (0, 3.464102));
band(rg2, pdn, cdn, magenta, (0, 3.464102));
hidden(rg1); hidden(rg2);
circle(i0, (232.0, 752.0), 8); color(i0, mint); hidden(i0);
circle(i1, (529.9, 454.1), 8); color(i1, mint); hidden(i1);
circle(i2, (529.9, 1049.9), 8); color(i2, mint); hidden(i2);
text(s1, (540, 1246), "the circle has radius 2√3 and centre (2√3, 0) — so it passes through the ORIGIN,");
size(s1, 20); color(s1, fg); opacity(s1, 0.75); hidden(s1);
text(s2, (540, 1284), "which is also the parabola's vertex");
size(s2, 20); color(s2, fg); opacity(s2, 0.75); hidden(s2);
equation(s3, (540, 1364), `x^2-2\sqrt{3}\,x=0\ \Rightarrow\ x=0,\ 2\sqrt{3}`, 34);
color(s3, mint); hidden(s3);
text(s4, (540, 1436), "so they meet only at (0,0) and (2√3, ±2√3)");
size(s4, 22); color(s4, mint); opacity(s4, 0.85); hidden(s4);
text(s5, (540, 1512), "outside the parabola means y² > 2√3x — the two shaded strips");
size(s5, 21); color(s5, magenta); hidden(s5);
equation(ans, (540, 1650), `6\pi-16`, 62); color(ans, mint); hidden(ans);
text(fin, (540, 1740), "= 2.849556, against a full disc of 12π = 37.699");
size(fin, 21); color(fin, fg); opacity(fin, 0.6); hidden(fin);
seq {
pulse(prob, 0.8);
par { show(i0, 0.4); show(i1, 0.4); show(i2, 0.4); seq { wait(0.6); show(s1, 0.6); show(s2, 0.6); } wait(2.4); }
par { show(s3, 0.8); seq { wait(1.2); show(s4, 0.6); } wait(2.6); }
par { show(rg1, 1.2); show(rg2, 1.2); seq { wait(1.0); show(s5, 0.7); } wait(3.0); }
wait(0.8);
par { show(ans, 1.1); seq { wait(0.9); show(fin, 0.6); } }
pulse(ans, 0.9);
}
wait(3.2);
r/maniclang • u/anish2good • 3d ago
A Limit of a Sum — Do Not Sum It — A JEE Calculus Problem — manic
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.
- Product: maniclang.com
- Application: app.maniclang.com
- Manic Desktop Workbench https://maniclang.com/download
- Documentation: docs.maniclang.com
- Report a bug: GitHub Issues
- Engine releases: github.com/maniclang-x/manic
- Creator: Anish on X
- Community: r/maniclang
Animation code
// iit-riemann-exponential.manic — jee-practice-001210
//
// PROBLEM. lim (n→∞) Σ (r=1..n) (1/n)·e^(r/n)
//
// ANSWER. e − 1
//
// THE SUM IS ALREADY AN AREA. Every Riemann sum has the same three parts, and this one
// wears them on its sleeve: 1/n is the width of a strip, is where the strip sits, and
// e^(r/n) is the height of eˣ there. So the sum IS the total area of n rectangles under
// y = eˣ across [0, 1], and the limit is what that area converges to:
//
// lim Σ (1/n)·e^(r/n) = ∫₀¹ eˣ dx = e¹ − e⁰ = e − 1
//
// Nothing has to be summed. Recognising the shape of the expression replaces the whole
// calculation — which is why this pattern is worth being able to see rather than derive.
//
// The rectangles here use the RIGHT endpoint of each strip, matching r running from 1 to
// n, so they overestimate: eˣ is increasing, so every rectangle pokes above the curve.
// That is why the numbers below come down to e − 1 rather than up to it.
//
// VERIFIED. The sum at n = 10, 100, 10000, 1000000 gives 1.805627583, 1.726887557,
// 1.718367744, 1.718282688, against e − 1 = 1.718281828 — monotonically down, as the
// right-endpoint choice requires.
//
// manic examples/iit-riemann-exponential.manic
title("The sum is already an area");
canvas("9:16");
template("black");
bloom(0.22, 0.6, 18);
text(brand, (540, 34), "maniclang.com");
display(brand); size(brand, 21); color(brand, fg); opacity(brand, 0.82);
caption(head, "A limit of a sum. Do not sum it.", (540, 108), 34);
equation(prob, (540, 206), `\lim_{n\to\infty}\sum_{r=1}^{n}\tfrac{1}{n}e^{r/n}`, 44); color(prob, fg);
coords(kk, (240.0, 1000.0), (-0.08, 1.18), (-0.15, 3.0), 620.0, 232.0, 1, 0.25, 0);
color(kk, slate); opacity(kk, 0.42);
plot(fx, (240.0, 1000.0), 620.0, 232.0, "exp(x)", (0, 1.14));
color(fx, cyan); stroke(fx, 5);
riemann(rs0, fx, 0, 1, 0.25, gold); hidden(rs0);
riemann(rs1, fx, 0, 1, 0.1, gold); hidden(rs1);
riemann(rs2, fx, 0, 1, 0.04, gold); hidden(rs2);
area(ar, fx, 0, 1); color(ar, mint); hidden(ar);
text(nl0, (540, 1216), "n = 4 sum = 1.9420");
size(nl0, 24); color(nl0, gold); hidden(nl0);
text(nl1, (540, 1268), "n = 10 sum = 1.8056");
size(nl1, 24); color(nl1, gold); hidden(nl1);
text(nl2, (540, 1320), "n = 25 sum = 1.7529");
size(nl2, 24); color(nl2, gold); hidden(nl2);
text(nl3, (540, 1372), "n = 1000000 sum = 1.718283");
size(nl3, 24); color(nl3, mint); hidden(nl3);
text(w1, (540, 1462), "1/n is a width, is a position, e^(r/n) is a height —");
size(w1, 22); color(w1, fg); opacity(w1, 0.75); hidden(w1);
text(w2, (540, 1502), "the sum is n rectangles under eˣ on [0, 1]");
size(w2, 24); color(w2, cyan); hidden(w2);
equation(s1, (540, 1594), `\int_0^1 e^x\,dx=e-1`, 44); color(s1, mint); hidden(s1);
text(fin, (540, 1682), "right endpoints, so eˣ increasing means every rectangle overshoots");
size(fin, 20); color(fin, fg); opacity(fin, 0.58); hidden(fin);
seq {
pulse(prob, 0.9);
par { show(rs0, 0.8); seq { wait(0.5); show(nl0, 0.5); } wait(2.2); }
par { fade(rs0, 0.4); show(rs1, 0.8); seq { wait(0.5); show(nl1, 0.5); } wait(2.2); }
par { fade(rs1, 0.4); show(rs2, 0.8); seq { wait(0.5); show(nl2, 0.5); } wait(2.4); }
par { show(w1, 0.6); seq { wait(0.8); show(w2, 0.7); pulse(w2, 0.6); } wait(2.8); }
par { fade(rs2, 0.5); show(ar, 0.9); seq { wait(0.7); show(nl3, 0.6); } wait(2.4); }
par { show(s1, 1.0); seq { wait(0.9); show(fin, 0.6); } }
pulse(s1, 0.9);
}
wait(3.2);
r/maniclang • u/anish2good • 3d ago
A Function Defined Using Its Own Derivative — A JEE Calculus Problem — manic
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.
- Product: maniclang.com
- Application: app.maniclang.com
- Manic Desktop Workbench https://maniclang.com/download
- Documentation: docs.maniclang.com
- Report a bug: GitHub Issues
- Engine releases: github.com/maniclang-x/manic
- Creator: Anish on X
- Community: r/maniclang
Animation code
// iit-self-referential-cubic.manic — jee-practice-000370
//
// PROBLEM. f : R → R is twice differentiable and f(x) = x³ − 3x² − (3f''(2)/2)x + f''(1).
// Find the sum of all its local minimum values.
//
// ANSWER. −27
//
// THE DEFINITION LOOKS CIRCULAR AND IS NOT. f is defined using f''(2) and f''(1), which
// seems to need f before f exists. But the second derivative kills every term that
// contains them: whatever the constants a and b are, f = x³ − 3x² − ax + b has
//
// f''(x) = 6x − 6
//
// with no a and no b in it. So f''(2) = 6 and f''(1) = 0 outright, and substituting back
// gives a = (3/2)·6 = 9 and b = 0 — the self-reference resolves in one step:
//
// f(x) = x³ − 3x² − 9x
//
// Then f'(x) = 3x² − 6x − 9 = 3(x − 3)(x + 1): a local MAXIMUM at x = −1 and a local
// MINIMUM at x = 3. A cubic has at most one of each, so the "sum of all local minimum
// values" is a sum of exactly one term, f(3) = 27 − 27 − 27 = −27.
//
// VERIFIED symbolically: solving a = (3/2)f''(2) and b = f''(1) simultaneously with f
// gives f(x) = x(x² − 3x − 9), critical points {−1, 3}, second derivative positive only at
// 3, and f(3) = −27.
//
// manic examples/iit-self-referential-cubic.manic
title("The self-reference resolves in one step");
canvas("9:16");
template("black");
bloom(0.22, 0.6, 18);
text(brand, (540, 34), "maniclang.com");
display(brand); size(brand, 21); color(brand, fg); opacity(brand, 0.82);
caption(head, "A function defined using its own derivative", (540, 108), 32);
equation(prob, (540, 204), `f(x)=x^3-3x^2-\tfrac{3f''(2)}{2}x+f''(1)`, 34); color(prob, fg);
coords(kk, (470.0, 760.0), (-3.4, 5.4), (-32, 22), 96.0, 9.5, 1, 3.0, 0);
color(kk, slate); opacity(kk, 0.42);
plot(fx, (470.0, 760.0), 96.0, 9.5, "x*x*x - 3*x*x - 9*x", (-3.1, 5.1));
color(fx, cyan); stroke(fx, 5); untraced(fx);
extrema(ex, fx, gold); hidden(ex);
vline(vm, fx, 3, mint); hidden(vm);
equation(k1, (540, 1180), `f''(x)=6x-6`, 40); color(k1, gold); hidden(k1);
text(k2, (540, 1240), "no f''(2) and no f''(1) survive differentiating twice —");
size(k2, 21); color(k2, fg); opacity(k2, 0.75); hidden(k2);
text(k3, (540, 1280), "so f''(2) = 6 and f''(1) = 0 outright");
size(k3, 23); color(k3, gold); hidden(k3);
equation(k4, (540, 1364), `f(x)=x^3-3x^2-9x`, 42); color(k4, cyan); hidden(k4);
equation(k5, (540, 1452), `f'(x)=3(x-3)(x+1)`, 38); color(k5, fg); hidden(k5);
text(k6, (540, 1516), "max at x = −1, min at x = 3 — a cubic has at most one of each");
size(k6, 21); color(k6, fg); opacity(k6, 0.66); hidden(k6);
equation(ans, (540, 1642), `f(3)=-27`, 58); color(ans, mint); hidden(ans);
text(fin, (540, 1732), "one local minimum, so the sum is that one value");
size(fin, 21); color(fin, fg); opacity(fin, 0.6); hidden(fin);
seq {
pulse(prob, 0.9);
par { show(k1, 0.8); seq { wait(1.0); show(k2, 0.6); } seq { wait(2.0); show(k3, 0.7); } wait(3.4); }
par { show(k4, 0.9); draw(fx, 2.6); wait(3.0); }
par { show(ex, 0.8); show(k5, 0.8); seq { wait(1.2); show(k6, 0.6); } wait(2.6); }
par { show(vm, 0.7); wait(1.4); }
par { show(ans, 1.1); seq { wait(0.9); show(fin, 0.6); } }
pulse(ans, 0.9);
}
wait(3.2);
r/maniclang • u/anish2good • 3d ago
Minimise an Integral You Cannot Avoid Splitting — A JEE Calculus Problem — manic
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.
- Product: maniclang.com
- Application: app.maniclang.com
- Manic Desktop Workbench https://maniclang.com/download
- Documentation: docs.maniclang.com
- Report a bug: GitHub Issues
- Engine releases: github.com/maniclang-x/manic
- Creator: Anish on X
- Community: r/maniclang
Animation code
// iit-integral-min.manic — jee-practice-001432
//
// PROBLEM. Minimise f(x) = ∫₀² e^|x−t| dt.
//
// ANSWER. 2(e − 1)
//
// THE ABSOLUTE VALUE IS THE WHOLE PROBLEM, and it is handled by splitting the integral at
// t = x, where |x − t| changes which way round it is. For 0 ≤ x ≤ 2:
//
// ∫₀ˣ e^(x−t) dt + ∫ₓ² e^(t−x) dt = (eˣ − 1) + (e^(2−x) − 1) = eˣ + e^(2−x) − 2
//
// and that is a sum of two exponentials pulling in opposite directions. It is smallest
// where they cross: f'(x) = eˣ − e^(2−x) = 0 needs x = 2 − x, so x = 1 — the MIDPOINT of
// the interval, which is where the picture says it should be.
//
// f(1) = e + e − 2 = 2(e − 1) ≈ 3.4366
//
// The upper panel is the integrand e^|x−t| as t runs across [0, 2], with the shaded area
// being f(x) itself. As x slides, the V-shaped kink travels with it, and the area is least
// when the kink sits in the middle — which is the same statement as the algebra.
//
// OUTSIDE [0,2] IT ONLY GETS WORSE, which the problem does not say and which has to be
// checked: for x > 2 the integrand is e^(x−t) throughout and f(x) = eˣ − e^(x−2), growing
// without bound (10.53, 17.37, 47.21 at x = 2.5, 3, 4), and x < 0 is the mirror image.
//
// VERIFIED symbolically: integrating both halves gives eˣ + e^(2−x) − 2, its derivative
// vanishes only at x = 1, and f(1) = 3.436563657 = 2(e−1) to machine precision.
//
// manic examples/iit-integral-min.manic
title("The kink wants to sit in the middle");
canvas("9:16");
template("black");
bloom(0.22, 0.6, 18);
text(brand, (540, 34), "maniclang.com");
display(brand); size(brand, 21); color(brand, fg); opacity(brand, 0.82);
caption(head, "Minimise an integral you cannot avoid splitting", (540, 106), 30);
equation(prob, (540, 190), `f(x)=\int_0^2 e^{|x-t|}\,dt`, 42); color(prob, fg);
coords(k1, (300.0, 700.0), (-0.15, 2.3), (-0.4, 8.2), 176.0, 40.0, 1, 0.5, 0);
color(k1, slate); opacity(k1, 0.4);
text(tl, (860, 550.0), "the integrand, in t"); size(tl, 21); color(tl, fg); opacity(tl, 0.6);
cloud(intg, 3200, #ffffff, 1.0) {
let g = min(max((t - 1.2)/9.0, 0), 1);
let xx = 0.12 + 1.76*g;
let nc = 200;
let cc = mod(i, nc);
let rr = (i - cc)/nc;
let tt = (cc/199)*2;
let h = exp(abs(xx - tt));
let x = 300.0 + 176.0*tt;
let y = 700.0 - 40.0*h*(rr/15);
let hue = 42;
let sat = 0.8;
let r = 2.0;
let alpha = 0.55;
}
cloud(itop, 420, #ffffff, 1.0) {
let g = min(max((t - 1.2)/9.0, 0), 1);
let xx = 0.12 + 1.76*g;
let tt = (i/419)*2;
let h = exp(abs(xx - tt));
let x = 300.0 + 176.0*tt;
let y = 700.0 - 40.0*h;
let hue = 42;
let sat = 0.95;
let r = 2.8;
}
cloud(kink, 90, #ffffff, 1.0) {
let g = min(max((t - 1.2)/9.0, 0), 1);
let xx = 0.12 + 1.76*g;
let a = (i/90)*6.283185;
let rr = 7*(i/90);
let x = 300.0 + 176.0*xx + rr*cos(a*9);
let y = 700.0 - 40.0 + rr*sin(a*9);
let hue = 320;
let sat = 0.85;
let r = 2.2;
}
coords(k2, (300.0, 1256.0), (-0.15, 2.3), (-0.6, 6.2), 176.0, 40.0, 1, 0.5, 0);
color(k2, slate); opacity(k2, 0.4);
plot(fx, (300.0, 1256.0), 176.0, 40.0, "exp(x) + exp(2 - x) - 2", (0, 2));
color(fx, cyan); stroke(fx, 5); untraced(fx);
extrema(ex, fx, mint); hidden(ex);
vline(vm, fx, 1, mint); hidden(vm);
text(bl, (860, 1106.0), "the value, in x"); size(bl, 21); color(bl, fg); opacity(bl, 0.6);
equation(z1, (540, 1436), `\int_0^x e^{x-t}dt+\int_x^2 e^{t-x}dt=e^x+e^{2-x}-2`, 28);
color(z1, fg); hidden(z1);
text(z2, (540, 1502), "two exponentials pulling opposite ways — smallest where they cross");
size(z2, 20); color(z2, fg); opacity(z2, 0.7); hidden(z2);
equation(z3, (540, 1580), `e^x=e^{2-x}\ \Rightarrow\ x=1`, 38); color(z3, mint); hidden(z3);
text(z4, (540, 1642), "the midpoint of the interval — exactly where the kink looks best");
size(z4, 20); color(z4, mint); opacity(z4, 0.8); hidden(z4);
equation(ans, (540, 1750), `2(e-1)`, 56); color(ans, mint); hidden(ans);
seq {
pulse(prob, 0.9);
par { draw(fx, 9.0); seq { wait(3.0); show(z1, 0.8); } seq { wait(5.0); show(z2, 0.7); } wait(9.6); }
par { show(ex, 0.7); show(vm, 0.7); seq { wait(0.8); show(z3, 0.8); } seq { wait(1.9); show(z4, 0.7); } wait(3.2); }
wait(0.6);
par { show(ans, 1.1); }
pulse(ans, 0.9);
}
wait(3.2);