Hazel performance analysis · ^report(15) · 2026-10-15

Dress Up and its livelits, side by side

How does Messed Up Dress Up, the plain-JavaScript game Matthew's kids M and N play and design, perform today against the same features built as livelits in Hazel? And what could close the gap over the next eleven months? Matthew asked both on 2026-10-15. Dress Up is where each toy is tried first; ^report(2) described how its pieces became the Livelit Playground's slides. This report puts the two side by side with one measurement, a real drag of a slider, in one browser on one machine, and then guesses, as a guess, at what is reachable by September 2027.

Short answers. Dress Up 0.0.48 and Hazel's #2635 at 032fe3aca6, in headless Firefox 157 on Matthew's laptop; 8 real drags per set-up, each 1.5 seconds long. Medians, with the fastest and slowest. The method is at the end.

  1. Dress Up keeps up with the finger. During a 1.5-second drag of a slider it never froze: the longest gap between frames was one frame (17 ms) with no place and in the forest, two frames (33 ms) in the witch's woods, its busiest place. When the finger lifted, the picture was already final. One change costs it about 1.4 ms of JavaScript (Figures 1 and 3).
  2. A small livelit is close. Kids' Choice, a face drawn by a Hazel livelit, froze for 0.11 s per drag (0 to 0.42 s), its longest gap was 59 ms, and the face was final 0.13 s after the finger lifted. On 2026-10-09 that release took 4.6 s (Figure 5).
  3. A big one is not. Full Body, the whole Dress Up friend as Hazel code (167 items), froze for 2.9 s during a 1.5-second drag with the whole program on screen (2.4–4.0), and 3.2 s in UX mode. The picture was final 1.1 s after the finger lifted (1.2 s in UX mode). Hazel handled 13 of the drag's input events where Dress Up handled about 50, and each one it handled took about a second of the page's time (Figures 1 to 3).
  4. Per change, the gap is about 720 times for Full Body and 160 times for Kids' Choice. Profiled, one Full Body change costs about 1,010 ms of JavaScript on the page, Kids' Choice about 225 ms, and Dress Up's 1.4 ms. A frame is 16.7 ms (Figure 4).
  5. Most of the distance has already been covered. Full Body's release went from 8.6 s on #2597 to 1.1 s on #2635, Kids' Choice's from 4.6 to 0.13 s, and a Polygons click from about 11 s to 0.2 s, all within the last week (Figure 5).
  6. A guess for September 2027: Kids'-sized livelits immediate, Full Body a short lag. The fixes in hand and the measured shares of where a change's time goes point to small livelits at 30 to 60 ms per change, and Full Body at 100 to 300 ms: a drag that follows the finger at a few to ten pictures a second, not every frame. Every frame for a program of Full Body's size would take an evaluator several times faster per step, which this guess does not assume. This is speculation, argued step by step below (Figure 6).

Update, later on 2026-10-15: not like for like. The drags above set Dress Up beside Hazel, but the two do not draw the same amount. Counting each measured picture's shapes (below, Figure 7):

A 1.5-second drag: how long the page was frozen (s)Dress Up, no placearm length0Dress Up, forestarm length0Dress Up, witch's woodsarm length0Dress Up, eyeseye size0Kids' Choiceeye size0.11Full Body, whole programarm length2.90Full Body, UX modearm length3.20medians of 8; lines: fastest to slowest; frozen: gaps over 50 ms
Figure 1. The time the page was frozen (frame gaps over 50 ms) during a 1.5-second drag of one slider. Dress Up froze for none of it in any place; Hazel's Full Body was frozen for longer than the drag itself. Numbers in the table below.
From the finger lifting to the final picture (s)Dress Up, no placearm length≤ 0.05Dress Up, witch's woodsarm length0.06Kids' Choiceeye size0.13Full Body, whole programarm length1.09Full Body, UX modearm length1.20medians of 8; ≤ 0.05 s: already final at the first check
Figure 2. From the finger lifting to the final picture. Dress Up's picture is final as the finger lifts (the measurement checks every 50 ms, so ≤ 0.05 s means "at the first check"). Numbers in the table below.
One 1.5-second drag, frame by frame: each tick is a frame the page drew0 s0.5 s1 s1.5 s2 s2.5 s3 s3.5 sDress Uparm lengthfinger up112 frames; longest gap 17 ms; frozen 0.0 sHazel: Kids' Choiceeye size112 frames; longest gap 116 ms; frozen 0.3 sHazel: Full Bodyarm length, whole program91 frames; longest gap 267 ms; frozen 2.5 spink: gaps over 50 ms, when the page was frozen; dashed: the finger lifts; green dot: the picture shows the final value
Figure 3. One drag in each, frame by frame: each tick is a frame the page drew, from the pointer going down; pink spans are gaps over 50 ms. Dress Up draws a frame every 17 ms throughout, and its picture is final when the finger lifts. Kids' Choice stutters a little and follows 0.1 s after. Full Body draws in bursts between long freezes, and its picture is final about a second after the finger lifts. One drag per lane (the first measured one); all runs are in the data.

One drag, three ways

A slider drag is the commonest thing the kids do in Dress Up, and the simplest thing a livelit can be asked to do: one number changes, many times a second, and a picture that depends on it redraws. The same drag was made in each set-up: a real pointer, driven over WebDriver, pressing the slider's thumb, moving 15 times, 100 ms apart, and lifting, to a value not drawn before. A loop in the page records every frame from the press until the picture shows the final value. The set-ups:

Set-upLongest gap between frames (ms)Frozen, gaps over 50 ms (ms)Finger up to final picture (ms)Input events the slider fired
Dress Up, no place
arm length
17 (17–33)0 (0–0)51 (50–52)50 (22–86)
Dress Up, the forest
arm length
17 (17–34)0 (0–0)51 (50–52)49 (22–88)
Dress Up, the witch's woods
arm length
33 (33–34)0 (0–0)64 (50–75)50 (22–87)
Dress Up, no place
eye size
17 (17–33)0 (0–0)51 (50–52)52 (35–84)
Hazel: Kids' Choice
eye size
59 (49–100)109.5 (0–416)130.5 (117–159)15.5 (13–53)
Hazel: Full Body, whole program
arm length
591.5 (217–950)2900 (2384–4048)1091 (1014–1212)13 (6–44)
Hazel: Full Body, UX mode
arm length
875.5 (484–1267)3201.5 (2172–4050)1196.5 (1056–2206)12.5 (6–43)

Medians of 8 drags after 2 warm-ups, fastest to slowest in brackets. Dress Up's "finger up to final picture" of about 50 ms is the measurement's first check after the release: its picture was already final. Input events are what the browser fired for the slider: a page that is busy gets fewer, since the browser folds the moves it could not deliver into one.

The difference is not in what is drawn: Full Body draws the same friend as Dress Up's no-place set-up. It is in what a change costs. Dress Up handles every input the browser fires, about 50 in a 1.5-second drag, and redraws for each within a frame. Hazel handles about 13 to 15, because the page is busy with the last one when the next arrives; for Kids' Choice each takes a frame or a few, for Full Body about a second.

Noise. The same Full Body drag measured this morning, on the same build, froze for medians of 4.1 and 3.2 s in two sessions, against 2.9 s this evening; the session matters more than any difference under about a second. Dress Up's numbers did not vary between its runs beyond one frame.

The cost of one change

One change, start to finish, in JavaScript on the page (log scale)1 ms10 ms100 ms1 sone frame, 16.7 msDress Upredraw the friend and the scene1.4 msKids' Choiceeye size, profiled225 msFull Body, whole programarm length, profiled1,010 msFull Body, UX modearm length, profiled1,251 ms
Figure 4. The JavaScript one change costs on the page, log scale, against one frame (16.7 ms, dashed). Dress Up's is timed directly (1.4 ms, the median of 15 samples of 40 changes each, in three places). Hazel's are from this morning's profiles of the same build (6 changes of Full Body, 4 of Kids' Choice), and profiled times run long, sometimes by half: Kids' Choice's unprofiled release to redraw is 130 ms.

For a drag to follow the finger at every frame, a change has to fit in a frame, 16.7 ms, with the browser's own drawing. Dress Up's fits about ten times over. Kids' Choice's is about 8 to 13 frames, Full Body's about 60. Where a Full Body change's second goes was profiled on the way to this report, on the same build: the editor's update takes about 860 ms of it, and within the update the livelit views about 450 ms. Over a whole drag, about 40% of the page's time is a check of the whole program for what can be reused: for every preview of a dragged value, the page compares the program with the last one, piece by piece. The evaluation itself runs in the worker, off the page, and the picture waits for it.

How far it has come

How far it has come: then and now (log scale)0.1 s1 s10 sPolygons: a click adds a point2026-10-09, the same day11 s0.20 sKids' Choice: release to redraw2026-10-09 afternoon → today4.6 s0.13 sFull Body: release to redraw10-12, full branch → today8.6 s1.09 s
Figure 5. Then and now, log scale. Polygons and Kids' Choice from ^report(1) (2026-10-09), Full Body's release on #2597 from ^report(8), each against today. The fixes behind them are in reports 001 to 014; several are Andrew Blinn's, from #2469, the modular editors.

A week ago, a slider release in Kids' Choice took 4.6 seconds; today a drag of it nearly keeps up. The largest steps were analysing only the changed item instead of the whole program (^report(8): statics 5.4 s to 38 ms per change), a reuse check by hash in the worker, requests that carry only what changed (^report(13)), and views that re-evaluate only the calls whose inputs changed (^report(1)). What is left is not one large mistake, but the cost of a general system doing for a whole program what Dress Up does for one figure, at every change.

What could close the rest, by September 2027

This section is speculation. Matthew's work on Hazel runs, as far as he knows now, through the next eleven months. Here is what we think that time could buy, step by step, each with what it rests on. The steps' sizes are measured today; that they add up as written is the guess.

The time from a slider change to its picture: today, and a guess for September 2027 (log scale)every framefeels immediatea lag you noticewaiting10 ms16.7 ms50 ms100 ms200 ms500 ms1000 ms2000 msDress Up (plain JS)today 18 msKids' Choiceguess: 30–60 mstoday 130 msFull Bodyguess: 100–300 mstoday 1,091 msdots: measured today (Dress Up: its cost per change plus one frame); dashed: a guess, argued in the section below, not a measurement
Figure 6. Today's time from a change to its picture (dots, measured: Kids' Choice and Full Body are release to redraw; Dress Up is its cost per change plus one frame), against bands of how it feels, and a guess for September 2027 (dashed). The guess is argued in the list below.
  1. Weeks: stop checking the whole program for a dragged value's preview. About 40% of a Full Body drag's time on the page is that check (profiled). A preview changes one number in one livelit's model; nothing else needs comparing. With it gone, and only the newest value kept while a change is in flight (built and measured on the way here; only 1.5 to 3 of a drag's 13 changes could be folded, so it matters once changes are cheap), a Full Body drag would freeze roughly half as long.
  2. One to three months: evaluate the views in the worker. A prototype, built on the way here, moved the livelit views off the page: about 540 ms less on the page per change in UX mode and 220 ms with the whole program, with the picture no sooner, and Kids' Choice 45 ms later. Its value is that the page keeps drawing frames while the worker works: the freezes shrink even where the wait does not.
  3. Three to six months: evaluate only what a change touches. ^report(10) found that a slider changes 8 of Full Body's 49 shapes, and that 75 to 84% of the evaluation steps run today are in calls whose inputs did not change; named by caller, call site and count (^report(11)), only 13 to 25% of a picture's evaluation would need to run. ^report(12) showed a move of a group need not touch its points. This is incremental computation inside one binding, the research behind Adapton, and the step most likely to take Full Body's evaluation from about a second toward a tenth of one.
  4. Alongside: draw only what changed. The page redraws a livelit's whole picture as a new tree and compares it with the last (^report(5)). With the worker sending what changed, only the 8 shapes would be redrawn.
  5. Not assumed: a faster evaluator. Hazel's evaluator is an interpreter, compiled to JavaScript with js_of_ocaml. Dress Up's code is JavaScript itself. Closing the last factor of ten for a program of Full Body's size, every frame, would take compiling livelit code or a much faster interpreter: possible, but larger than the steps above, and not in the guess.

Together: steps 1 and 2 make the page stop freezing; steps 3 and 4 cut the wait. That is how the guess lands Kids'-sized livelits at 30 to 60 ms per change (immediate, and a drag at 15 to 30 pictures a second) and Full Body at 100 to 300 ms (a short lag, a drag at 3 to 10 pictures a second). Dress Up stays the bar: the kids will keep playing the plain-JavaScript game, and each toy will keep reaching them there first.

What next

  1. Measure the two drag fixes already built (keeping only the newest value, and views in the worker) in an interleaved comparison: both builds served at once, alternating drag by drag, ten or more each, so the session's drift cancels.
  2. Skip the whole-program reuse check for a drag's preview, and measure a Full Body drag with it.
  3. Split one Full Body change's second into stages with marks rather than a profiler (the page's update, the request, the worker's evaluation, the reply, the views, the redraw), so this report's guess rests on measured shares.
  4. Make the same drags on an iPad, in Safari, where M plays: the kids' own device sets the bar.
  5. Run this report's drags again every month, on #2635's head and the newest Dress Up, and add a dated update with the new figures.

Update, later on 2026-10-15: how much each picture draws

How much each measured picture draws: its shapes (log scale)20501002005001,0002,000Hazel: Full Bodyone picture of 167 items49froze 2.9 s in a dragHazel: Kids' Choicea face93froze 0.11 sHazel: the Scenes slidethe largest in the series; not timed here (report 010)570Dress Up: the friend alonethe same friend as Full Body46never frozeDress Up: the forestthe friend in a place457 · 16 animatednever frozeDress Up: the witch's woodsits busiest place1,452 · 463 animatednever froze
Figure 7. Each measured picture's shapes, log scale, with what its drag did. Hazel's pictures are the friend (Full Body) and a face (Kids' Choice); the Scenes slide, the largest Hazel picture in this series, is shown for scale, not timed here. Dress Up's friend alone is the like-for-like pair with Full Body; its places draw 10 to 30 times as much.
PictureShapesAnimated (SMIL)GroupsAll elementsIn a 1.5-second drag
Hazel: Full Body490049froze 2.9 s (whole program)
Hazel: Kids' Choice93044138froze 0.11 s
Dress Up: the friend alone4602277never froze
Dress Up: the forest4571693584never froze
Dress Up: the witch's woods1,452463551,983never froze (longest gap 33 ms)

Shapes are the picture's drawing elements (paths, rectangles, circles, ellipses, lines, polylines, polygons, text and uses of a shared drawing), counted in the same browser and on the same builds as the drags: Dress Up 0.0.48's picture after each set-up, and the largest picture on each Hazel slide after it loaded. A shape count is a rough measure of a picture's complexity, not of its cost: Dress Up's witch's woods also animates 463 of its parts, every frame, which is the heavier part of drawing it (0.0.55 made those animations cheaper). The counts are in this page's data and in 015/data/shapes.json.

How this was put together