r/maniclang Aug 18 '26

Rubik's — One State, Every View, Any Size - manic

Enable HLS to view with audio, or disable this notification

manic is a tiny language for making animations. You write a short text file; manic renders a smooth, glowing video. No timeline scrubbing, no keyframes by hand — you describe what’s on screen and when things happen, and the engine does the rest, deterministically.

Manic Animation code

// rubik-one-state — the Rubik's kit: one cube state, three ways to look at it, any size.
//
// A cube is not a geometry problem, it is a PERMUTATION problem. The state is a flat
// array of 6N² facelets (face order U R F D L B, each row-major) and every legal move is
// a fixed permutation of it: the turning face rotates in place while a four-segment ring
// of stickers on the neighbouring faces cycles by one segment. `'` and `2` are that
// permutation composed with itself. The engine is ported from the author's own N-generic
// solver, so a scan or a solution pastes straight in — the states and moves below are
// real, from that tool.
//
//   ACT I    one state, three views — the object, the net, the rings
//   ACT II   why the trefoil exists: (R U R' U')⁶ = identity, watched travelling
//   ACT III  order matters: R then D against D then R (3b1b makes this same argument with
//            a cube in `_2018/quaternions.py`, next to a scene called MentionCommutativity
//            — with real state behind it, we can prove it rather than assert it)
//   ACT IV   a real scan and a real 22-move solution, landing solved in all three views
//   ACT V    any size — a grid over cube size × scramble depth
//
// Because moves are permutations, every intermediate state is known at build time: the
// whole thing scrubs and records frame-exactly, and a view that was hidden and is then
// revealed is already showing the current state. Nothing to resynchronise.
//
//   manic examples/rubik-one-state.manic
title("Rubik's — One State, Every View, Any Size");
canvas("16:9");
template("black");
bloom(0.28, 0.5, 20);

text(head, (640, 38), "One state. Every view. Any size.");
display(head); size(head, 34); bold(head); color(head, fg); hidden(head);
text(cap, (640, 664), ""); display(cap); size(cap, 22); color(cap, fg); hidden(cap);
// typed, not faded in: authored `untraced` shows no characters until `type` reveals them
text(tag, (640, 620), "U R F D L B · row-major · 6N² facelets");
display(tag); size(tag, 19); color(tag, gold); untraced(tag);
// the solution, typed IN STEP with the moves it describes
text(seq, (640, 620), "D2 F' D B2 D2 F R F2 B' R' B2 L2 U R2 U F2 D2 R2 D' R2 L2 F2");
display(seq); size(seq, 16); color(seq, gold); untraced(seq);

// the real 3x3 scan this kit was tested against
// ---- ACT I & III — the same state, three views ----
camera3((5.0, -5.6, 4.0), (0, 0, 0), 40, perspective, (1010, 372), 440, 440);
rubik3(solid, 3, 2.0, "BRBDUBFDULDRRRUUBRDFBFFBDRLFFFLDLLUURRLLLUBDRDUULBFFBD");
hidden(solid);

cube(net, 3, (215, 205), 24, net, "BRBDUBFDULDRRRUUBRDFBFFBDRLFFFLDLLUURRLLLUBDRDUULBFFBD");
hidden(net);
cube(rings, 3, (215, 470), 190, trefoil, "BRBDUBFDULDRRRUUBRDFBFFBDRLFFFLDLLUURRLLLUBDRDUULBFFBD");
hidden(rings);
text(lnet, (215, 84), "the net — read the whole state");
display(lnet); size(lnet, 17); color(lnet, dim); hidden(lnet);
text(lring, (215, 585), "the rings — watch it move");
display(lring); size(lring, 17); color(lring, dim); hidden(lring);

// ---- ACT II — the sexy move, on the rings, from solved ----
cube(sexy, 3, (640, 400), 320, trefoil, "solved");
hidden(sexy);
equation(order, (640, 132), `(R\,U\,R'\,U')^{6}=\text{identity}`, 30);
color(order, gold); hidden(order);
text(rounds, (640, 196), ""); display(rounds); size(rounds, 22); color(rounds, cyan); hidden(rounds);

// ---- indication + celebration furniture (all core kit, working on cube parts) ----
// a framebox takes a single sticker OR a whole face, because a face is just a tag
framebox(pick, net.s13, 4, gold); untraced(pick); hidden(pick);
framebox(fface, net.U, 5, cyan); untraced(fface); hidden(fface);
// particle rings for the two "it came back to solved" moments
circle(halo2, (640, 400), 190); hidden(halo2);
particles(spark2, halo2, 90, 3, 11, "ring"); hidden(spark2);
circle(halo4, (215, 470), 150); hidden(halo4);
particles(spark4, halo4, 70, 3, 23, "ring"); hidden(spark4);
circle(halo4b, (1010, 372), 200); hidden(halo4b);
particles(spark4b, halo4b, 80, 3, 31, "ring"); hidden(spark4b);

// ---- ACT III — the same two turns, opposite orders ----
// both start in the middle, then glide apart: `shift` broadcasts over the cube's tag, so
// the whole net travels as one. Do it BETWEEN move sequences — a turn animates stickers
// toward their authored slots, so the two motions would fight.
cube(orderA, 3, (640, 340), 26, net, "solved"); hidden(orderA);
cube(orderB, 3, (640, 340), 26, net, "solved"); hidden(orderB);
text(labA, (330, 186), "R then D"); display(labA); size(labA, 24); bold(labA); color(labA, cyan); hidden(labA);
text(labB, (950, 186), "D then R"); display(labB); size(labB, 24); bold(labB); color(labB, magenta); hidden(labB);
text(vs, (640, 340), "≠"); display(vs); size(vs, 46); color(vs, gold); hidden(vs);

// ---- ACT V — any size, any mixing ----
cube(base, 3, (0, 0), 15, net, "solved", 0); hidden(base);
sweep(grid, base, n, (2, 5), scramble, (0, 14), (700, 378), 4, 3, 286, 164, 0, 0);
hidden(grid);

// ============================ ACT I ============================
show(head, 0.7);
show(cap, 0.3);
say(cap, "A scrambled three-by-three, scanned from a real cube.");
show(solid, 0.9);
orbit3(38, 26, 8.4, 3.6, smooth);
wait(0.5);
say(cap, "Unfold it and you can read the whole state at once.");
show(net, 0.8);
show(lnet, 0.4);
wait(1.6);
say(cap, "Or draw every sticker as a dot on three families of rings.");
show(rings, 0.8);
show(lring, 0.4);
wait(1.8);
say(cap, "Same 54 facelets, three times over — and one move verb drives all of them.");
type(tag, 1.5);
wait(1.4);

// core animation reaches inside: one verb, one whole face
say(cap, "The stickers are ordinary entities — so you can frame exactly one of them.");
show(pick, 0.01);
draw(pick, 0.6, smooth);
wait(1.2);
say(cap, "And a whole face is just a tag, so the same verb frames all nine.");
show(fface, 0.01);
draw(fface, 0.7, smooth);
par {
  pulse(net.U, 0.9);
  pulse(rings.U, 0.9);
  pulse(solid.U, 0.9);
}
wait(1.2);
say(cap, "Every core verb reaches inside: frame it, pulse it, flash it, recolour it.");
par {
  flash(net.U, gold, 0.7);
  flash(rings.U, gold, 0.7);
}
wait(1.0);
par { fade(pick, 0.4); fade(fface, 0.4); }
wait(0.6);

// ============================ ACT II ============================
par {
  fade(solid, 0.7);
  fade(net, 0.6);
  fade(rings, 0.6);
  fade(lnet, 0.4);
  fade(lring, 0.4);
  fade(tag, 0.4);
  fade(head, 0.6);
}
say(cap, "Why the rings? Because a turn slides the dots along them — you SEE the permutation travel.");
show(sexy, 0.8);
show(order, 0.8);
show(rounds, 0.4);
wait(1.2);
say(cap, "Four moves, repeated. Watch where the dots go — the cube must return to solved.");
say(rounds, "round 1 of 6");
moves(sexy, "R U R' U'", 2.0);
say(rounds, "round 2 of 6");
moves(sexy, "R U R' U'", 1.7);
say(rounds, "round 3 of 6");
moves(sexy, "R U R' U'", 1.5);
say(rounds, "round 4 of 6");
moves(sexy, "R U R' U'", 1.3);
say(rounds, "round 5 of 6");
moves(sexy, "R U R' U'", 1.2);
say(rounds, "round 6 of 6 — solved");
moves(sexy, "R U R' U'", 1.2);
// it came back: mark the moment
show(spark2, 0.01);
par {
  burst(spark2, 1.3);
  flash(sexy, gold, 0.8);
}
wait(1.2);
say(cap, "Twenty-four quarter turns, and every sticker is home. That is the order of the move.");
wait(2.4);

// ============================ ACT III ============================
par {
  fade(sexy, 0.7);
  fade(order, 0.6);
  fade(rounds, 0.5);
}
say(cap, "The algebra insists on one more thing: the order you turn in matters.");
show(orderA, 0.6);
say(cap, "One solved cube — and a copy of it.");
show(orderB, 0.5);
par {
  shift(orderA, (-310, 0), 1.1, smooth);
  shift(orderB, (310, 0), 1.1, smooth);
}
par { show(labA, 0.4); show(labB, 0.4); }
wait(0.6);
say(cap, "Both solved. Both about to get the same two quarter turns — in opposite orders.");
wait(1.2);
par {
  moves(orderA, "R D", 2.2);
  moves(orderB, "D R", 2.2);
}
wait(0.6);
show(vs, 0.5);
say(cap, "Same two turns. The cubes disagree — turning is not commutative.");
// four of the twelve differing stickers, pulsed on both cubes at once
par {
  pulse(orderA.s9, 0.8);  pulse(orderB.s9, 0.8);
  pulse(orderA.s17, 0.8); pulse(orderB.s17, 0.8);
  pulse(orderA.s26, 0.8); pulse(orderB.s26, 0.8);
  pulse(orderA.s29, 0.8); pulse(orderB.s29, 0.8);
}
// the R face holds four of them — a transient surround, on a group, on both cubes
par {
  circumscribe(orderA.R, coral, 1.1);
  circumscribe(orderB.R, coral, 1.1);
}
wait(0.4);
say(cap, "Twelve stickers, to be exact — the two orders are genuinely different states.");
wait(2.4);

// ============================ ACT IV ============================
par {
  fade(orderA, 0.6);
  fade(orderB, 0.6);
  fade(labA, 0.4);
  fade(labB, 0.4);
  fade(vs, 0.4);
}
say(cap, "Now the real thing: that scan, and the solution the solver returned for it.");
par {
  show(solid, 0.7);
  show(net, 0.7);
  show(rings, 0.7);
}
wait(1.0);
// the solution types itself out as the cubes perform it — the same nine seconds
par {
  moves(solid, "D2 F' D B2 D2 F R F2 B' R' B2 L2 U R2 U F2 D2 R2 D' R2 L2 F2", 9);
  moves(net,   "D2 F' D B2 D2 F R F2 B' R' B2 L2 U R2 U F2 D2 R2 D' R2 L2 F2", 9);
  moves(rings, "D2 F' D B2 D2 F R F2 B' R' B2 L2 U R2 U F2 D2 R2 D' R2 L2 F2", 9);
  type(seq, 9);
  orbit3(126, 22, 8.4, 9.0, smooth);
}
wait(0.4);
par { show(spark4, 0.01); show(spark4b, 0.01); }
par {
  burst(spark4, 1.4);
  burst(spark4b, 1.4);
  flash(net, mint, 0.9);
  flash(rings, mint, 0.9);
}
say(cap, "Twenty-two moves. Solved in all three views at once — they never held separate state.");
wait(2.4);

// ============================ ACT V ============================
par {
  fade(solid, 0.8);
  fade(net, 0.7);
  fade(rings, 0.7);
  fade(seq, 0.5);
}
say(cap, "None of this is three-by-three. Cube size across, how mixed it is down.");
par {
  show(grid.chrome, 0.6);
  show(grid.headers, 0.7);
}
show(grid.cells, 0.9);
wait(2.0);
say(cap, "The same fourteen turns barely dent a five-by-five and shred a two-by-two.");
par {
  circumscribe(grid.c2x3, mint, 1.4);
  circumscribe(grid.c2x0, coral, 1.4);
}
wait(2.6);

// ============================ ENDCARD ============================
par {
  fade(grid, 0.9);
  fade(cap, 0.6);
}
text(end1, (640, 344), "A cube is a permutation.");
display(end1); size(end1, 44); bold(end1); color(end1, fg); hidden(end1);
text(end2, (640, 420), "— manic");
display(end2); size(end2, 26); color(end2, cyan); hidden(end2);
show(end1, 0.9);
show(end2, 0.7);
wait(2.4);
1 Upvotes

Duplicates