Equanim
A JSON animation spec where every visual property is a maths expression, designed for AI to write and anything to render.
- Role
- Designed and built it
- When
- 4 days, March 2026
- Stack
- TypeScript, mathjs, Canvas
- Tests
- 279 assertions
The idea
Most animation formats describe keyframes. Equanim describes functions. Every number in every frame is a maths expression evaluated at time t. A circle that grows is r = 50 * t. A wave that decays is amplitude * exp(-decay * t) * sin(k * s - omega * t). The whole animation is a function.
A spec is pure JSON: no code, nothing to execute, no renderer assumptions. Hand it to any renderer and it plays. And it is written for language models, which are good at equations and bad at nudging keyframes around a timeline.
Every number is an equation
The double pendulum is the example that convinced me the format works. Its motion is chaotic, so it can't be faked with keyframes. The spec declares the physics once as an ode_system, integrated with RK4, and every object reads the solved angles as functions like phys_th1(t).
"variables": { "L1": { "default": 2.5, "min": 0.5, "max": 5.0 }, "L2": { "default": 1.8, "min": 0.3, "max": 4.0 }, "m1": { "default": 2.0, "min": 0.5, "max": 5.0 }, "m2": { "default": 1.0, "min": 0.5, "max": 5.0 }, "g": { "default": 9.8, "min": 1.0, "max": 30.0 },},{ "id": "phys", "type": "ode_system", "solver": "rk4", "step": 0.005, "derivatives": { "th1": "w1", "th2": "w2", … } },
Running live, RK4
Variables
The decisions that mattered
Data, not code
Expressions are strings evaluated by mathjs, never JavaScript. A renderer can validate a spec's shape without running any of it, and a generated spec can't do anything but draw.
t-first
The default clock is local:
truns 0 to 1 over an object's own window. Moving an object's window changes when and how fast it plays, not whether it is visible. Real seconds are one multiply away.Physics as data
An
ode_systemholds state and derivatives and names a solver. Its solved state becomes functions sibling objects can call, so arms, bobs and the trace all read one simulation.Built for a model to write
The schema is flat and regular on purpose. The test for the format is whether a model, given only
spec.md, can write a valid and interesting spec without help.
How I built it
About four days in March 2026. The first commit is a CLAUDE.md, and the agent worked from that and from spec.md, which is the source of truth: when the renderer and the spec disagree, the spec wins.
Every core module has its own test file, 279 assertions with no test framework. That is what let an agent change the evaluator or the solver without me re-checking every example by eye.