how it is drawn
MENAGERIE is a board of thirty-odd creatures — human, cat, dog and imp, each breathing on its own clock, drawn as if by hand. A board is rolled fresh each time; one you keep, or a creature you make, is a file of JSON. It runs in the browser with no build step: the board is here, the code is here.
This page is the medium — what they are drawn with, on three axes: the goofy outline, the goofy material and the goofy fur, plus the goofy type the name screen's card is written in, the goofy letters, the shapes and colours. No textures and no brush images; the hand comes from rules. It is all three.js, every stroke and fill a triangle.
Every figure below is drawn live by the code that draws the creatures — the same
src/stroke.js and
src/medium/, at the board's
own scale, so nothing here can drift from what the board does. INK BOIL (or I) sets them boiling; a reload
draws them all in another hand.
THE GOOFY OUTLINE
Every line on a creature is a goofy outline — a kind from GOOFY_OUTLINES in
src/medium/outlines.js. A part names no kind, no line function and no width. It names its line's role:
ink.contour(path, { color, size }) closes a shape and ink.line(…) runs open, down to a dot — with at
most a size on it. One switch, BOARD_LINES, says which kind draws each role; change a name there and every
line of that role changes.
A kind is named the pen, then the hold. One pen is on the table, the pencil, in three holds: STROKE (once, full width — mass) · SLINE (thin, the pen lifts — detail) · BROKEN (three times over itself — contour). One line each, the same path:
A kind also draws at a size — S · M · L, each kind naming its own three: STROKE 0.004 · 0.007 · 0.012, SLINE 0.002 · 0.003 · 0.005, BROKEN 0.0035 · 0.006 · 0.011. A ladder is the kind's own rather than a multiple of one width, because a hairline blown up to STROKE's L stops being a hairline. A size is the only way a width is said — no multiplier on top, so a width that is not on a ladder cannot be drawn. Most lines are M, fine detail S, and two lines on the whole board L. The hold does not change with the size: SLINE still lifts at L, BROKEN still stacks its ghosts at S. AGAIN redraws in another hand.
in use on the board
The two rows read the switch live: 45 contours and 119 open lines, at three widths and no others, so no creature wears a two-line ear beside a one-line eye. What the table does not cover: the two bands (a hat's colour laid as a thick stroke, a fill in disguise), the emoji glyphs, and the marks a goofy material and the goofy fur lay down — the medium's own, not a creature's lines. All of them the same pencil.
contour — 45 of them: the head and bodies, the ears, hats and hands, the eyes and eyewear, the nose, the mouth's parts. line — 119: lids, brows and lashes, whiskers, limbs and feet, hair strands, horns, the floor, and every dot and dash. S for the finest, M for the rest, L for an imp's horn and a stub tail.
the pencil
A stroke is no line — WebGL draws none thick enough. It is a chain of quads: two samples make four corners, each pushed to both sides along its normal by the half width, cut into two triangles. The pencil takes one normal per point and hands it to the quads on both sides, so a corner closes; on a closed line the last quad joins the first, which is the seam it does not have.
PENCIL_STROKE · PENCIL_SLINE · PENCIL_BROKEN — one pencil, three holds. Its sines run per world length rather
than per line, so the wander comes out the same size wherever it lands; a closed line snaps them to whole cycles and never
overshoots, which is the rest of why it has no seam. Every number is in PENCIL in src/stroke.js.
The ink is opaque, which is what lets the creature in front hide the one behind.
PENCIL_STROKE is full width, laid once — every line on the board. PENCIL_SLINE is a hairline whose pen lifts: the skips are in world units along the path as handed over, so the kind breaks in the same places at every size, and none falls near an end or on a line too short to spare it. PENCIL_BROKEN stacks the ghost: two thinner passes, one at a fifth of the ink and one at half, each slipping to one side or the other, with the line laid last on top. SLINE and BROKEN are built and on nothing — the switch says what the board draws.
A dot or a dash needs no kind of its own: the pencil keeps the ends of anything shorter than 0.05 and sheds nothing there, because an overshoot would run a dot half again to twice as long and a crumb is the size of the dot.
One habit per row, each row the row above plus one. The sixth is the whole pencil — that is PENCIL_STROKE; the seventh adds the ghost, and stacking that is PENCIL_BROKEN.
proportions.wobble multiplies the jitter per
creature. On one constant the whole board falls into the same handwriting.THE GOOFY MATERIAL
A goofy material is what a surface is made of, the way a 3D material is — how its area is filled, as channels. (Goofy, like the outline and the fur, to keep it apart from the GPU material underneath.) There are two channels and they paint the same thing, the colour of the surface:
base — the fill-up: always opaque, in the part's colour.
texture — hatch, scratch, dab, speckle or wash, laid over the base and clipped to the contour.
pattern — the creature's own mark (stripes, dots, spots, hatching), clipped to the contour and laid last, over the
material: it is something on the animal, not part of the surface, and under a hatched or dusted texture it was being buried.
That is the whole of it — no opacity channel, and grain is the sheet's job, not a material's. A part names a material and
hands over the path and the colour (fills.paint(path, "FLAT", { color, offset })); it never picks a technique, and
every tone the texture adds is a shade of the part's colour.
A material is three things, and the editor's MATERIALS card lays them out in that order. A texture — which of the
six techniques fills the area: GRAPHITE, CHARCOAL, OIL, INK, WATERCOLOUR or MARKER (the material slot). A density — which of the five
value steps it is laid at, low to high (the density slot). A colour — the box the surface is painted from. FLAT,
the fill-up with nothing laid on it, is not a texture a creature can be made of: it is what the whites of the eyes are filled with.
A creature wears two materials. The main one covers the head's side — the head, the ears, the horns, the hair, the
hat, the muzzle and the face — in the skin box's colour. The body's covers the torso, the arms and hands, the sleeves, the
legs and boots, and the tail, in the cloth box's colour, with a texture and a density of its own (bodyMaterial,
bodyDensity). A face and a torso are two surfaces, and one hand may reach for a second tool, or press differently,
between them: the roll hands the body the main's texture on three creatures in four and the main's density on four in five, and
its own on the rest. pattern rides along too.
the five textures, at the five densities
Every texture in GOOFY_MATERIALS
gets a row of shader balls — the same ball at the five density steps, light · stipple · scribble · hatch · black from
low to high, named for the way graphite makes each. A step is an amount of the medium — how many marks a material lays — and
the ground is the part's colour at every step (it used to be pulled toward the technique's own tone as well, and a creature's palette
colour darkened as its density rose). The step is the creature's density slot — one of the five, picked by the roll, so every step turns up on every
species; the colour decides the marks' tone, the step how many.
A mark has to be lighter than what it is drawn on, so on a dark ground every technique turns around — the lower row of each pair.
A mark is a light. Ink's scratches, graphite's rules and oil's paint are all drawn in the same tone — the ground tinted, its own hue carried toward white — so they come up out of the surface. Every tone stays in the part's own family: a blue part gets lighter and deeper blues, where mixing toward the light ink to lighten turned its marks grey. Charcoal alone keeps dark marks; one technique going the other way is what keeps the four apart.
And no mark is ever white. A lighter tone is the colour tinted — its own hue carried toward white — never a multiply,
which clips: ink's scratches on a pale part came out #ffffff and ran across a cream creature as hard white lines. A
colour with no room left to water turns ink around too: its ground is laid on deeper the more solid the step, and the scratch
opens back toward the part's own colour.
One creature is one hand and one pressure, but not one throw of the dice: a texture's noise is the part's own — its place and size on the board — and the hand takes a small swing of its own on each. Without it the head, the ears, the muzzle, the hat and the body all scattered their marks to the same values in the same places, and a creature read as one stamp repeated.
the paper, the GPU material and the boil
Underneath there is exactly one GPU material, a flat MeshBasicMaterial with vertex colours shared by every
mesh. No depth test, so renderOrder decides all the stacking, and a layer's fills and ink bake into one geometry — one
draw call per layer.
The paper is the board's one shader
(src/scene/paper.js):
procedural GLSL grain on a single plane, no texture and no tile. It is drawn twice — once behind everything, and once
over the whole board after the emoji, so the creatures come out under the paper rather than on it. The pass over the top is not a
fade: its alpha follows how far a cell is off the sheet's mean, so flat sheet does nothing and only the speckle lands. The ink
keeps its black, which is what lets the creature in front hide the one behind.
visible cycles once every 1.5–1.9 s. It belongs to the medium, not to motion — the lines shiver even
in the bind pose.the colors
Only the ones that hold up on paper; push the saturation and the hand-drawn feel breaks
immediately. And they go in as linear: three.js reads vertex colors as linear space,
so an sRGB hex fed raw brightens — the ink #2b2724 would come out a mid grey.
Every hex passes through hexToRgb → srgbToLinear in src/color.js, nothing bypasses it.
makeGrid caps them); scattered everywhere they would stop popping.THE GOOFY FUR
Hair is neither a contour nor a filling — in 3D it is a groom, here the goofy fur:
ink.fur(path, "SCRIBBLE", { color, passes, spread }), named from GOOFY_FUR. A part hands over the path
and the colour; passes, width and spread ride as a style's volume and the rest is the fur's own. One fur stands in the table,
SCRIBBLE, and every hairstyle is grown with it:
SCRIBBLE — the scribble
The same path drawn over and over, each pass pushed further out from the root so the mass fans away from the face, every point waving on its own and a share of each push leaning sideways.
THE GOOFY TYPE
One screen writes words: the name screen's card — the name, the kind, the moves, the rarity. A name comes in any
script, so the card does not draw a word glyph by glyph: it traces a real font (src/medium/type.js). A line is
written onto a canvas in Noto Sans and Noto Sans KR, served from fonts/ and loaded whatever is typed; the canvas's
coverage is traced at half into rings (marching squares), each ring simplified, and a ring inside an odd number of others is a hole.
Then the rings go down in the pencil — every point pushed about by a wiggle of its own, the shape filled with its holes, each ring
edged in a pencil line — and each boil frame wiggles them its own way, so the words boil with the drawing. A kanji, a hanja or an
emoji the two fonts do not hold is written in the platform's font and traced all the same: an emoji comes out an ink silhouette.
typeWith, the same
function the card writes with.THE GOOFY LETTERS
The other way to draw a word, and the first: the goofy letters (src/medium/letters.js) — 26 capitals, ten
digits and a few marks, as strokes, not a font. Every glyph is a list of strokes measured in cap heights — x across its
width, y from the baseline to the cap — and each stroke goes down as one line of the same pencil, so a word wanders, sheds and boils
like the drawing it sits on. The ♥ and the ★ are filled under their line. They are Latin capitals only, which is why the card writes
in the type above; they are kept.
letterWith, one pencil line a stroke, in the letters' own hand (half the board's
wobble, so a word a few ink widths tall still reads). Every glyph here is read off GLYPHS.a corner is two strokes
The pencil re-samples a line at a fixed step (PENCIL.step, 0.01) and walks round a corner by cutting it. On a head
that is nothing; on a letter a few steps tall — a small word on a card — a cut corner is a round A. So a corner is
two strokes meeting, never one bent line, and where the pen runs past a stroke's end it runs past the way a hand does. A
curve is one stroke; a loop — O, 0, 8, a dot — is closed.
the pens
A letter's pen is a share of its cap height — the letters' own ladder, the way every outline kind names its own, so a word's line grows with the word and a weight that is not on the ladder is not drawn. A title takes L and everything else M.
LETTER_PENS.Capitals only. A character the letters have no glyph for — Hangul, kana, an emoji, a lower-case or an accented letter — they cannot write at all. A Korean pencil hand, built from jamo rather than 11,172 syllables, would have been a piece of work of its own; tracing a font (the goofy type, above) writes every script there is.
THE SHAPES
A head is not a circle but a lumpy mass. One recipe, blobPath, makes every closed shape on the board: a superellipse bent by noise, squared and tapered by two knobs.