Hazel performance analysis · ^report(2) · 2026-10-11

From a game to a slide deck: Messed Up Dress Up in Hazel

Dedicated to a resonance between research and play — see the dedication at the end.

How a plain HTML/JS prototype, played by real players, became Hazel slides you can use: hair on the head, a neck, arms, a shirt with a style and a size, hands, eyes and a mouth. Branch #2635 (livelits × modular editors), with the slides kept in step on #2597. Measured 2026-10-11 on dev builds, in headless Firefox.

4 + 1
new playground slides (Arms, Hands, Eyes, Mouth), and Shoes and Hair grown
0.3–0.9 s
a change on Shoes and Hair or Arms, until the picture redraws
0.5–1.7 s
a change on Eyes or Mouth
1–2.5 s
a change on Hands, the heaviest: 1 s for round hands, 2.5 s for seven alien fingers

In short: ^report(1) took a livelit slider release from 4.6 s to about 0.13 s. That made the slides worth building out, so we did: each feature was tried first in Messed Up Dress Up, a one-file HTML/JS game, by its players, and then formulated in Hazel, on the Livelit Playground slides. The formulations work and look like the game. They also show the next performance problem plainly: a slide's cost now grows with the number of points it draws, because the picture is computed, and then turned into SVG text, by Hazel code, one point at a time.

The method: prototype in HTML/JS, formulate in Hazel

Messed Up Dress Up is one HTML file: a friend to dress, and tabs of controls for the face, the eyes, the hair, the neck, arms and legs, the shirt, the hands, the pants, swimwear, the feet and costumes. It is quick to change, and it has players, who say what they want next and what is confusing. HTML/JS is still the lighter-weight place to prototype, in a lot of ways.

The Hazel side is the Livelit Playground deck. Its pictures are lists of polygons, each drawn in its own ^polygons editor, with named anchors on their corners, and set into one picture by matching anchors: the shirt's neck to the head's chin, the head's crown to the hair's. A ^picture livelit shows the result, ^sliders and ^choices set the numbers and styles. So each of the game's features, already tried by players, was rewritten in that vocabulary: polygons, anchors, and functions from the controls' values to polygons.

Messed Up Dress Up: a cartoon friend and a panel of controls.
The prototype: one model, one render function, a tab of controls per part.

The features, one at a time

Shoes and Hair

Hair on the head, not behind it

The slide drew the hair behind the head, so short hair showed as a cap peeking out around it. Drawing it on top instead ran into the anchors: a hairdo is placed by its crown, and an anchor is a corner of its polygon, so every hairdo's outline ran through the crown, and drawn over the head it would have cut a V from the crown down to the face.

The game draws long hair in two layers, so the slide does that for every hairdo: the hairdo as it was, drawn behind the head (its crown still places it), and a top, the hair over the scalp down to a fringe, drawn on the head. The two are placed together by the crown, then split by name to stack: body, shoes, the hair's back, the head, the hair's top.

Short, long and curly hair, each drawn over the top of the head down to a fringe.
Each hairdo's top follows the head's outline and leaves the face open: a zigzag fringe, side-swept bangs, small curls. Pressing a hairdo redraws the picture in 0.3–0.4 s.

446dfbe74c

A neck, and a frame that fits

The head sat on the shirt's collar. Now a rectangle runs from the shirt's neck anchor to the chin, its length set by a slider. Its anchors sit in the middles of its edges, and it is 16 longer than what shows: placed, it moves 8 down under the collar and the head 8 down over its top, so no gap shows. As in the game, it is drawn first, behind the shirt, so it shows in the V of the collar.

A long neck pushed the head out of the viewer's fixed frame. The viewer now frames what is drawn: the usual frame, grown to every point plus a margin and made the viewer's shape, so a taller picture is drawn smaller rather than cut off.

The figure with neck lengths 0, 28 and 60; at 60 the whole figure is drawn smaller to fit.
Neck 0, 28 (the start) and 60. The slider redraws in 0.4 s.

2b8b102429

Arms (new slide)

Arms, and a shirt that is a system of polygons

The game's three arm sliders, with its ranges and starting values: the angle out of the shoulders (0 straight out, 90 down, -90 up), the length and the width. An arm is a band from a point just inside the shoulder to the hand, and the hand a disc. The angle turned out to be the most useful control, as the players had found.

Arms that move need sleeves that move, so the shirt is no longer one polygon. It is a system of them, with the arms' position as a parameter: the torso, drawn in its editor without sleeves, and a sleeve on each arm computed from its angle, length and width. Then three more of the game's shirt controls: the style (t-shirt, long sleeves, tank top; the sleeve covers 38%, 95% or none of the arm), and the width and height, which stretch the torso about the neck. The shoulders and hips are anchors, so the arms and pants follow, and the pants take the shirt's width too.

The figure with its arms at 55, 0, -70 degrees, and long and wide at 80.
Arms at 55 (the start), straight out, raised, and long and wide by the sides: the sleeves go with them. Each slider redraws in 0.5–0.7 s.
A t-shirt, long sleeves, a tank top, a wider and taller shirt, and a narrower and shorter one.
The shirt's style, width and height. A change redraws in 0.6–0.9 s.

489e2db3f7, 2bbbc6d85d

Hands (new slide)

Six kinds of hands

The game's six hand styles, chosen with buttons, and its sliders for the size, the fingers of a human hand (5 is a hand, 1 a thumbs-up, 100 a fringe) and of an alien hand, and the claws' length. A hand is made in its own frame, the arm along x, then turned to the arm's angle and mirrored on the left. The game draws hands with SVG ellipses, round-capped lines and curves; here they are polygons: ellipses of 20 sides, fingers as bands with round ends, talons as sampled curves. Human and alien hands are drawn as the game draws them, every part first a little bigger in the outline's color and then in skin, so palm and fingers make one shape with one outline.

Human, round, mittens, star, claw, long claw, alien, seven-fingered alien and twelve-fingered human hands.
All six styles, and the claws at 220%, seven alien fingers and twelve human ones.

This is the heaviest slide, and the one that shows what comes next. A change takes about 1 s with round hands and 2.2–2.5 s with twelve human or seven alien fingers. A profile of alien-finger changes: about 1.7 s per change on the main thread, in ^picture's view, which is Hazel code turning every point of the picture into SVG text, and about 1.5 s in the worker, computing the points (profiled times; real ones are smaller). Both grow with the number of points: both hands, in two layers, with pads, come to over a thousand. Two cheap steps helped: fewer points per shape (a pad is a 12-gon, a fingertip arc 5 points), and making the hand once for both arms; together they took seven alien fingers from 4.7 to 2.5 s.

744f0e0ad5

Eyes and Mouth (new slides)

A face, in the game's own units

The game's face comes from the Kids' Choice slide, in its own units: a head 160 high. The new slides work in those units and set the result on the head drawn here, by its crown and chin, so the face moves and grows with the head. They build on Shoes and Hair, not on Hands, so they stay quicker.

Eyes: plain, stars, hearts or x eyes, an iris color, and the game's sliders for size, gap, shape (round to narrow), angle (the outer corners rising), iris, eye whites and shine. A plain eye is a pupil, an iris and whites when chosen, squashed and then slanted, with a shine toward the light in the picture's own frame. The eyes keep the gap between them, edge to edge, however big, narrow or slanted they are; the default gap puts the usual eyes where they always were. The head grows wider and taller to hold them, so they stay 8 inside its outline.

Nine heads from the Eyes slide: plain eyes; big eyes with blue irises and whites, the head grown wide to hold them; narrow slanted green eyes; star, heart and x eyes; plain eyes with no gap between them; plain eyes far apart, the head widened; very big eyes, the head grown taller and wider.
A style redraws in 0.4–0.6 s, a slider in 0.5–1.2 s (one run each). The eyes keep their gap, and the head grows to hold them. (Finding the head's frame once, not per point, took the sliders from 1.5–1.8 s.)

Mouth: a smile from a frown to a grin that opens (a smile like a D, a frown the other way up), normal, spiky or no teeth, and a tongue that is in, sticks out, or is not there, of any length, width and color. Its lips are the game's quadratic curves. The game clips the inside of the mouth to the lips; there is no clipping in a list of polygons, so here the teeth and tongue are regions between the lip curves. A deep mouth stretches the head's lower half, as in the game, and the head rises on its neck.

A grin, a frown, an open mouth with teeth and tongue, spiky teeth, a tongue sticking out, a deep open grin stretching the head.
A smile change redraws in 0.8 s, an opening, teeth or tongue change in 1.3–1.7 s.

658e850942, b78faab21a

Making it usable

Playing the slides, rather than reading them, turned up the rough edges between "it works" and "you can use it".

A probe in an eye's pane was cut off

A livelit's eye opens a pane under it with its syntax, and a probe there drew its samples past the end of the line, which in the pane is its right edge: they were clipped, and the pane's scroll bar covered the model's row. The pane's rows are rows the livelit keeps clear in the editor, so the samples now run on past the pane, as they do beside any other line (47afffcf10).

Before: the pane's row half covered by a scroll bar and the samples cut off. After: the row whole and the samples running on.

Buttons were cut off

A choice's box was 27 columns, and the six hand styles and seven tongue colors did not fit. Making its width follow the buttons did not work: every choice came out the same width, because a livelit's shape is fixed for the livelit, not computed per use. So it is 36 columns (b580d84aa1).

One cell per control, its settings behind the eye

Each control was two cells: one naming the configured livelit (let ^hand_size_dial = Drawing.^slider("hand size", 2, 80), code, no GUI) and one using it (let hand_size = ^^livelit(^hand_size_dial(14)), with the GUI), named differently, so it was not clear which cell had the GUI. Every slider is used once, so its settings now go in its use, and the eye shows them on demand: the cell shows the slider, and its eye the livelit's head (Drawing.^slider("hand size", 2, 80)) and the model. The choices could not follow yet: a direct use cannot have a type application in its head (Drawing.^choice@<Hand>(cases)(Human) reads as applying the livelit's module to Human), so their two cells stay, renamed to pair up: ^hand_style and hand_style (7bb257be0e).

Two outline modes: 🏗️ dev and 🖼️ UX

With modular editors, the outline picks which parts of a program show as cells. For playing, you want the GUIs; for building, the code. So the outline has two modes, each with its own open cells, neither disturbing the other: 🏗️ dev, the architecture, shows what was picked; 🖼️ UX, the art made in it, shows every livelit's GUI to start. Either can show anything, and a GUI row has a 🖼️ chip saying whether UX mode shows it. On the Mouth slide, UX mode opens its 24 GUIs; the mode and its picks survive a reload (38a6df851e).

The Mouth slide in UX mode: the outline's header with the dev and UX switch, GUI rows with chips, and a stack of slider and choice cells.
UX mode on Mouth: only the GUIs, in the order the program has them.

What a change costs, slide by slide

slidechangeuntil the picture redraws
Shoes and Haira hairdo; the neck0.3–0.4 s
Armsan arm slider; the shirt's style or size0.5–0.9 s
Handsround hands; a style0.9–2.1 s
Handstwelve human or seven alien fingers2.2–2.5 s
Eyesa style; a slider0.5–1.3 s
Mouththe smile; opening, teeth, tongue0.8–1.7 s

For comparison, a Kids' Choice release is about 0.13 s (^report(1)), and the game itself takes under a millisecond. The difference is in what is drawn: Kids' Choice's face is a hand-written view, while these slides compute their pictures, polygon by polygon and point by point, in Hazel, and then print every point.

What next

  1. Draw the points without Hazel code per point. ^picture's view spends about 1.7 s of a Hands change printing points. A view that reuses the drawing of unchanged shapes, or a builtin that prints a list of points, would take most of that away. To measure first: how much the view already reuses for shapes that did not change.
  2. Let a direct use have a type application in its head, Lib.^f@<T>(args)(model), so choices can be one cell with their settings behind the eye, like sliders.
  3. More of the game: the shirt's collar, legs and pants styles, the feet, colors for everything, and in the end one slide with the whole friend.
  4. Seeing big types: the cursor inspector cuts off a long type with no way to see the rest (#2674, filed on dev).

Try it

How these were measured

Dedication

We dedicate this report to a resonance: a recurring set of good feelings and thoughts that comes from helping Hazel while being playful and creative with kids.

It involves people, Matthew's kids most of all. Messed Up Dress Up is theirs: they play it, they named it, and much of what is in it, and so in these slides, began as something they asked for. Here the research and the play turned out to be one activity, and it has been, in Matthew's words, a very rewarding experience.

We hope others can find it too, if it is their desire.