how kipirti wobbles

eight steps. every drawing on this page is made by the app's own engine, right now.
01

The spine

A line is not a curve. It is points at even spacing — however fast your hand crossed the screen, every point sits the same distance from the one before it. Because the wobble runs on top of that, one end of a line never shakes more than the other.

A finger or a pen jumps several pixels between frames; addPoint() fills the gap in SPACING steps and writes a pressure onto every point — real pressure from a stylus, a flat 1 from a mouse. Everything after this works on that array.

02

The hand

A hand does not tremble at one speed. A slow drift carries the whole line, a middling waver ripples it, a fine tremor eats at its edge. Summed, the three stop being noise and start being a hand.

Three scales weighted .55 / .30 / .15 and divided by their RMS, so the WOBBLE slider still means what it always meant. The slowest layer holds across boil frames and the finest is re-rolled every one: the construction stays put while the ink is laid again. hand(), src/brushes.js.

03

The boil

Nothing moves. The same line is drawn three ways and flipped between — what hand-drawn animation has done for a century. Three frames on the left; on the right, those three cycling.

The clock rounds to a tick with Math.floor(now · speed · fps), then mod 3. The same tick twice means the same pixels twice — which is exactly what the editor leans on when it skips a repaint. Multi-pass brushes (SKETCH, GHOST) overlay adjacent frames, so they do not cycle: mod 3 would hand them the same three every tick and the drawing would die.

04

The material

A brush says how a line moves. A material says what it is made of. Two different questions, and for a long time there was only one answer: every line was a round-capped path of one colour. The same spine can now be graphite or watercolour.

A material never learns which brush it is painting; it is handed a spine and a toolkit and answers in its own vocabulary: ribbon (a band filled between two rails, not a stroked path), rim, grit, streaks. GRAPHITE throws crumbs past its own edge and bites paper-coloured squares back out of it; INK punches holes in the band as the nib dries. PLAIN is the line the app shipped with — it stays the default, because every drawing ever saved is made of it.

05

The brush

Fourteen brushes, and not one of them is a texture. Each is a small function saying where to push the line's points this frame: SPRING swells about its centroid, WAVE walks a ripple down the length of it, RUNNER erases the line and runs a single dot around where it was.

A brush writes disp(p, i, t, A, info) — the point, its index, the time, the amplitude, and on request the local normal and the centroid. A new brush is one entry in that list. Saving, the feed, GIF export and the previews all read it.

06

Its own clock

Every line freezes its own seed, speed and frame rate the moment it is drawn. Not a detail — the only decision that matters here: boiling in unison would read as video, not as a drawing that is alive.

The seed travels with the line, so a drawing you publish wobbles on someone else's screen exactly the way it wobbled on yours. The six lines below are the same brush on the same path; only seed and fps differ.

07

The frame

A drawing is not one picture. The strip holds frames, each with its own layers, and the one before shows through faintly while you draw — the onion skin. Frames boil even when they are held, because the wobble belongs to the line, not to the animation.

Frames live in film.frames as their own layer sets; doc always reflects the frame being edited. GIF and MP4 export run through the same renderDoc(), so what you save is what you were looking at.

08

The seed

The only thing saved is numbers. Points, brush, material, size, amplitude, speed, fps and a seed — not a picture. The same numbers rebuild the same drawing on any machine, wobble and all.

The wire format is one flat array: a 400-point stroke drops from ~30 kB of JSON to ~4 kB, which is the difference between a feed that loads and one that does not. A material only travels when it is not the default, so every drawing published before materials existed still packs byte for byte the same.

go draw