DOM ENGINEV0.21.3FIZLET

2026-10-31 FIELD NOTES · SIZE

How small is small
The kernel, every brick and the hero budget, in bytes.

"Tiny" means nothing without a number. Fizlet's kernel is 3,413 bytes gzip on 0.21.0, against a budget of 4,096 that fails the build when crossed. Every brick has its own budget, a page hero has one too, and the site shows only numbers the build measured. Here is each number, and how we get it.

By Chris Cooley ↗ · · 3.3 KB gz

The budgets that matter most

  • 3,413 B

    the kernel (budget 4,096 B)

  • 7,128 B

    the page build: kernel + slot + input + fx (budget 7,168 B)

  • 10 KB

    a hero's live chunk: kernel, bricks and the scene's own code

  • 13,371 B

    fizlet.js: one script tag, every brick but debug

How we measure

One function measures everything: scripts/sizes.mjs. It bundles each entry with esbuild (ESM, ES2022, minified), gzips the result and compares it with the entry's budget. 1 KB is 1,024 bytes. A brick's number includes every part of the kernel it imports, so it is what a page pays for that brick alone.

npm run size runs it in CI and fails over budget. The website calls the same function while it builds, so the numbers on the budgets page and on every brick page are the ones CI checked, never typed. The site test measures again and fails if a page disagrees.

The kernel, release by release

  • 0.1.0: about 2.6 KB. The shared clock, the fixed step, auto-pause, pools, the event ring, the seeded random and the ledger.
  • 0.3.5: 2,839 → 3,106 B. Hardening: options checked at setup, a throwing hook stops only its own engine, destroy() idempotent.
  • 0.5.0: 3,106 → 3,275 B. The page profile: settle (no frames once nothing moves) and the fps cap.
  • 0.7.0: 3,275 → 3,347 B. engine.timing and engine.live: typed-array slots, so reading them never boxes a number.
  • 0.19.0: 3,439 B (+17 B). The fps cap now caps requestAnimationFrame itself: a capped hero asks for 30 frames a second instead of one per vsync.
  • 0.21.0: 3,413 B. Headroom, no behaviour change: the ledger's private fields got one-letter names, and its host-changed style check and put-back became shared helpers.

Each step bought something you can test. That is the rule for a budget: it moves only with a reason, and the changelog says how much.

Every brick, gzip, on 0.21.0

  • steer 1,156 B · sound 1,042 B

    Movers on typed arrays with seek, flee, arrive, wander and patrol; a synth with nine cues and no audio files.

  • coach 1,220 B · input 1,335 B · collide 1,265 B

    Tutorial steps and tips; keys, pointer and a touch stick as intents; a fixed-capacity spatial hash.

  • rules 1,318 B · hud 1,325 B · camera 1,475 B · sprite 1,437 B

    Waves, drops and score; change-only readouts; pan and zoom of a DOM stage; pixel characters as data.

  • grid 1,603 B · presence 1,834 B · slot 1,819 B

    A board with A* paths and sight; lockstep input exchange; a lazy scene for an Astro slot.

  • perf 2,064 B · pad 2,074 B · embed 2,040 B · timeline 2,120 B

    The players' perf overlay; touch controls; a drop-in game toggle; keyframes on the kernel clock.

  • match 2,652 B · fx 2,797 B · ui 2,907 B + 5,139 B css · board-page 4,136 B

    Match flow; pooled particles and the blast; the Board UI keys; the host page as the board.

The hero budget

A landing-page hero's live chunk (kernel, bricks and the scene's own code) has a budget of 10 KB gzip. The page build takes 7,128 B of it, which leaves about 3 KB for the scene. A test fails if sound, debug, the pad, the HUD, match or the Board UI get into the page build.

The two story heroes on the home page fit: the layers scene's live chunk is 7,789 B and the fizz scene's 8,723 B. Neither loads until its slot nears the viewport, and both settle to zero frames once nothing moves.

When a budget had to move

fizlet/timeline missed its target. We aimed for 1,536 B. Keyframes, easing tables, stagger, labels, repeat and yoyo, controls and revert came to 2,050 B when it shipped in 0.19.0, so its budget is 2,304 B, and the changelog says so. On 0.21.0 it is 2,120 B.

fizlet/board-page moves when the kernel does. It bundles only the kernel's phase constants, but esbuild's renamer counts the whole engine module, so any kernel edit moves it by about 2 bytes. In 0.19.0 it sat at 4,095 of 4,096, so the budget went to 4,128 rather than chase renames; it is 4,193 now that its hit test uses steer's exact hypot, and on 0.21.0 it is 4,136 B.

Questions

Why gzip and not minified bytes?

Gzip is what crosses the wire for a script on almost every host, so it is what a visitor pays. Where a change is about minified bytes, the changelog gives them too.

Why is fizlet/arcade 24,327 B?

It is a shell, not a brick: it composes the kernel, board-page, input, the pad, sound, the HUD and the Board UI keys, so a page arcade game is only its rules and art. A card game that leaves out the skirmish HUD ships 18,334 B of it.

Do you compare with other engines?

No. We publish what we measured on our own code, with how we measured it.

All notes