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.
- 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).
- 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).
- 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).
- 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).
- 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).
- 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):
- Only one pair is like for like: the friend alone. Dress Up's friend with no place is 46 shapes; Hazel's Full Body, the same friend, is 49. On that pair Dress Up never froze, and Full Body froze 2.9 s in a 1.5-second drag.
- Dress Up's places draw up to 30 times as much, and move. The forest is 457 shapes, 16 of them animated; the witch's woods 1,452 shapes and 463 animations, about 30 times Full Body's picture. Dress Up never froze in either. The largest picture Hazel has drawn in this series, the Scenes slide, has 570 shapes (
^report(10)). - And the game is far bigger than any slide. Dress Up 0.0.48 was 12,647 lines, 24 places, 1,370 settings behind 402 controls, and 28 tunes synthesized in the page; Full Body is one picture of 167 items. So the comparison above is Hazel at the size it can handle today against Dress Up at that size: the gap in the short answers is the gap at equal size, and the game itself has grown well past what Hazel can draw.
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:
- Dress Up 0.0.48: the friend's "arm length" slider, alone and in two of its places (the forest, and the witch's woods, the place with the most moving parts); and the "eye size" slider, the closest match to Kids' Choice.
- Kids' Choice on the Livelit Playground: a face drawn by a livelit, with an "eye size" slider. A small program.
- Full Body on the Livelit Playground: Dress Up's friend written in Hazel (167 items), with an "arm length" slider; with the whole program on screen, and in UX mode, where the outline shows only the slider and the picture (
^report(8)measured both modes' releases).
| Set-up | Longest 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
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
^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.
- 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.
- 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.
- 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. - 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. - 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
- 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.
- Skip the whole-program reuse check for a drag's preview, and measure a Full Body drag with it.
- 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.
- Make the same drags on an iPad, in Safari, where M plays: the kids' own device sets the bar.
- 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
| Picture | Shapes | Animated (SMIL) | Groups | All elements | In a 1.5-second drag |
|---|---|---|---|---|---|
| Hazel: Full Body | 49 | 0 | 0 | 49 | froze 2.9 s (whole program) |
| Hazel: Kids' Choice | 93 | 0 | 44 | 138 | froze 0.11 s |
| Dress Up: the friend alone | 46 | 0 | 22 | 77 | never froze |
| Dress Up: the forest | 457 | 16 | 93 | 584 | never froze |
| Dress Up: the witch's woods | 1,452 | 463 | 55 | 1,983 | never 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
- Builds: Hazel #2635 (livelits × modular editors) at
032fe3aca6, a dev build; Messed Up Dress Up 0.0.48 (08abedf7bc), the game as published. - Browser and machine: headless Firefox 157 over WebDriver (geckodriver), a 1600×1100 window, a fresh profile for each set-up, on Matthew's laptop (Linux, x86-64), one browser at a time, 2026-10-15 between 18:55 and 19:08 Mountain time. Dress Up's music was muted.
- The drag: WebDriver pointer actions: move to the slider's thumb, press, 15 moves of 100 ms each to the target, release. Targets are values not drawn before (Full Body: 150 to 425 in steps; the others: a share of the slider's range). Two warm-up drags, then 8 measured, per set-up. A requestAnimationFrame loop in the page records every frame from just before the press; after the release, the page checks every 50 ms whether the largest picture's markup differs from before the drag, which ends the window. Frozen time is the sum of frame gaps over 50 ms in the window.
- Dress Up's cost per change: its
changed()(draw the friend and the scene, sync the controls, save the state) called for 40 changes of arm length, timed withperformance.now(), 5 samples in each of three places (no place, the forest, the witch's woods); JavaScript only, not the browser's drawing, which the drags cover. - Hazel's cost per change: from Gecko profiles of the same build taken the same morning (6 changes of Full Body in each mode, 4 of Kids' Choice), main-thread JavaScript per change. Profiled times run long; the unprofiled release times above are the check.
- History: the earlier numbers are from
^report(1)and^report(8), each measured with its own method on its own build; they are compared with today's for scale, not to the millisecond. - The numbers, for programs too: every number behind the figures and tables is also in this page, as JSON in its
<script type="application/json" id="report-data">element: each run's readings, the frame times of the drags in Figure 3, Dress Up's timing samples, and the profiled costs. The same numbers are files in015/data/, and the drivers that made them in015/drivers/(stall_any.pyfor any slider in either,drag_stall.pyanddressup_stall.pyfor the arm-length drags; they usedeck_regress.pyfrom #2635'sscripts/).