asciify-engine 4.23.0 → 4.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/CHANGELOG.md +315 -0
  2. package/README.md +71 -711
  3. package/dist/core.cjs +1 -1
  4. package/dist/core.js +1 -1
  5. package/dist/field.cjs +1 -1
  6. package/dist/field.d.cts +4 -1
  7. package/dist/field.d.ts +4 -1
  8. package/dist/field.js +1 -1
  9. package/dist/hover.cjs +1 -1
  10. package/dist/hover.js +1 -1
  11. package/dist/index.cjs +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/kit/chunk-2D2SG6FW.js +1 -0
  14. package/dist/kit/chunk-2JQ4MRIP.js +1 -0
  15. package/dist/kit/chunk-2KFBIOVS.js +1 -0
  16. package/dist/kit/chunk-2TQW2PIG.js +1 -0
  17. package/dist/kit/chunk-3F6FTBMN.js +1 -0
  18. package/dist/kit/chunk-4SHQA3CO.js +1 -0
  19. package/dist/kit/chunk-4XT5IKM3.js +1 -0
  20. package/dist/kit/chunk-6L2GAV5C.js +1 -0
  21. package/dist/kit/chunk-6XNIZ52V.js +1 -0
  22. package/dist/kit/chunk-6YVZZ3DO.js +1 -0
  23. package/dist/kit/chunk-A6EDU3UT.js +1 -0
  24. package/dist/kit/chunk-A73S2BCE.js +1 -0
  25. package/dist/kit/chunk-ANLBW5RI.js +1 -0
  26. package/dist/kit/chunk-B6GZ645M.js +1 -0
  27. package/dist/kit/chunk-BBOSHBZK.js +1 -0
  28. package/dist/kit/chunk-BJRPY7UB.js +1 -0
  29. package/dist/kit/chunk-CA5J25SV.js +1 -0
  30. package/dist/kit/chunk-CR7QS3B3.js +1 -0
  31. package/dist/kit/chunk-D4KCZSIP.js +1 -0
  32. package/dist/kit/chunk-EHXSXMBE.js +1 -0
  33. package/dist/kit/chunk-EHYFSX5K.js +1 -0
  34. package/dist/kit/chunk-EJ3W4FN6.js +1 -0
  35. package/dist/kit/chunk-EK4GIXIO.js +1 -0
  36. package/dist/kit/chunk-EPVZIGEZ.js +1 -0
  37. package/dist/kit/chunk-EWPT56TR.js +1 -0
  38. package/dist/kit/chunk-FIWIUXNV.js +1 -0
  39. package/dist/kit/chunk-G4BQYQJ6.js +1 -0
  40. package/dist/kit/chunk-GCIZKMJL.js +1 -0
  41. package/dist/kit/chunk-H4DBJULW.js +1 -0
  42. package/dist/kit/chunk-HMW5NRUV.js +1 -0
  43. package/dist/kit/chunk-IUX46UDR.js +1 -0
  44. package/dist/kit/chunk-JAM46FF6.js +1 -0
  45. package/dist/kit/chunk-JRSCTMFM.js +1 -0
  46. package/dist/kit/chunk-KBXVIAWS.js +1 -0
  47. package/dist/kit/chunk-KJJJORN6.js +1 -0
  48. package/dist/kit/chunk-KS3TL3S2.js +1 -0
  49. package/dist/kit/chunk-KTYCGGRO.js +1 -0
  50. package/dist/kit/chunk-L3P7ZDSK.js +1 -0
  51. package/dist/kit/chunk-LUOIH2EM.js +1 -0
  52. package/dist/kit/chunk-MIA6TUQC.js +1 -0
  53. package/dist/kit/chunk-MRDEO5HZ.js +1 -0
  54. package/dist/kit/chunk-NPJ77LDC.js +1 -0
  55. package/dist/kit/chunk-ODMQERGZ.js +1 -0
  56. package/dist/kit/chunk-ONVONNUH.js +1 -0
  57. package/dist/kit/chunk-OVCIHOOS.js +1 -0
  58. package/dist/kit/chunk-P6S6HSN3.js +1 -0
  59. package/dist/kit/chunk-PCGRRX5P.js +1 -0
  60. package/dist/kit/chunk-PDNKLLBI.js +1 -0
  61. package/dist/kit/chunk-PFNFW4ZI.js +1 -0
  62. package/dist/kit/chunk-PFQBEWCF.js +1 -0
  63. package/dist/kit/chunk-PND73YEV.js +1 -0
  64. package/dist/kit/chunk-PZAQLUAS.js +1 -0
  65. package/dist/kit/chunk-QKZYCROD.js +1 -0
  66. package/dist/kit/chunk-QTVMD62E.js +1 -0
  67. package/dist/kit/chunk-QZHZWO7N.js +1 -0
  68. package/dist/kit/chunk-SAQJAH2I.js +1 -0
  69. package/dist/kit/chunk-SE6MQQLB.js +1 -0
  70. package/dist/kit/chunk-SQGJ263V.js +1 -0
  71. package/dist/kit/chunk-SVXRMHUJ.js +1 -0
  72. package/dist/kit/chunk-UO4U32P7.js +1 -0
  73. package/dist/kit/chunk-V6GEQ5Y5.js +1 -0
  74. package/dist/kit/chunk-VFV7HZ3G.js +1 -0
  75. package/dist/kit/chunk-WT3XOWAI.js +1 -0
  76. package/dist/kit/chunk-XDNCXCR7.js +1 -0
  77. package/dist/kit/chunk-XPXMCXYI.js +1 -0
  78. package/dist/kit/chunk-XVB3B3FV.js +1 -0
  79. package/dist/kit/chunk-XXPDXJMQ.js +1 -0
  80. package/dist/kit/chunk-XYLLL2PQ.js +1 -0
  81. package/dist/kit/chunk-YBBYCUFP.js +1 -0
  82. package/dist/kit/chunk-YDP6566E.js +1 -0
  83. package/dist/kit/chunk-YL2AXWRA.js +1 -0
  84. package/dist/kit/chunk-ZCHGV2CV.js +1 -0
  85. package/dist/kit/chunk-ZYYLWSPY.js +1 -0
  86. package/dist/kit.cjs +1 -1
  87. package/dist/kit.d.cts +3 -1
  88. package/dist/kit.d.ts +3 -1
  89. package/dist/kit.js +1 -1
  90. package/dist/studio.cjs +1 -1
  91. package/dist/studio.js +1 -1
  92. package/package.json +3 -2
  93. package/skills/asciify-engine/SKILL.md +3 -3
  94. package/skills/asciify-engine/references/applying-asciify.md +1 -1
  95. package/skills/asciify-engine/references/kit.md +2 -2
  96. package/skills/asciify-engine/references/performance.md +33 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "asciify-engine",
3
- "version": "4.23.0",
3
+ "version": "4.25.0",
4
4
  "description": "Framework-agnostic ASCII art engine. Convert images, videos, and GIFs into ASCII art rendered on canvas, or run full-bleed GPU character fields with a fluid cursor wake.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -70,10 +70,11 @@
70
70
  },
71
71
  "files": [
72
72
  "dist",
73
- "!dist/*.map",
73
+ "!dist/**/*.map",
74
74
  "skills/asciify-engine",
75
75
  "README.md",
76
76
  "LICENSE",
77
+ "CHANGELOG.md",
77
78
  "examples/media-background.ts",
78
79
  "examples/studio-composition.ts",
79
80
  "examples/layered-composition.ts",
@@ -2,13 +2,13 @@
2
2
  name: asciify-engine
3
3
  description: Apply Asciify the way asciify.org does - ASCII components and backgrounds that belong in a real interface, inked from the theme, clear around text, inside the page's radii and motion. Use for UI components in characters (loading, status, toasts, tabs, AI states, charts, transitions, pages), animated ASCII backgrounds, and also photo, video and GIF conversion, GPU character fields, artwork looks for wallpapers, posters and prints, art styles and export, hover and motion, layered typography and rendering performance.
4
4
  metadata:
5
- tested-engine: "4.23.0"
5
+ tested-engine: "4.25.0"
6
6
  updated: "2026-10-17"
7
7
  ---
8
8
 
9
9
  # Asciify Engine
10
10
 
11
- Use the published `asciify-engine` npm package. Tested against **4.23.0**. Check the application's installed version and its public types before using newer options; a local checkout can contain unpublished changes.
11
+ Use the published `asciify-engine` npm package. Tested against **4.25.0**. Check the application's installed version and its public types before using newer options; a local checkout can contain unpublished changes.
12
12
 
13
13
  - Agent entry: https://asciify.org/skill (redirects to this Markdown document).
14
14
  - Components: https://asciify.org/components · Looks: https://asciify.org/looks · Backgrounds: https://asciify.org/backgrounds
@@ -84,7 +84,7 @@ This uses the Vercel Skills CLI to install the repository skill and its linked r
84
84
 
85
85
  ## Verify before handing off
86
86
 
87
- Run the checklist at the end of [applying-asciify.md](references/applying-asciify.md#10-checklist-before-you-finish) for kit work. For any integration, check the real page: readable subject and text in both themes, no stretching or unintended margins, working pointer and keyboard controls, mobile crop, reduced motion, autoplay failure, WebGL off, and cleanup after navigation. Observe several seconds of motion and a loop boundary. State the measured device and settings with any FPS figure; screenshots alone do not establish performance.
87
+ Run the checklist at the end of [applying-asciify.md](references/applying-asciify.md#10-checklist-before-you-finish) for kit work. For any integration, check the real page: readable subject and text in both themes, no stretching or unintended margins, working pointer and keyboard controls, mobile crop, reduced motion, autoplay failure, WebGL off, and cleanup after navigation. Observe several seconds of motion and a loop boundary. State the measured device and settings with any FPS figure; screenshots alone do not establish performance. What the engine guarantees a host page, and its default budgets, are in [performance.md](references/performance.md).
88
88
 
89
89
  Do not import nonexistent helpers such as `createRecorder` or `recordAndDownload`.
90
90
 
@@ -149,7 +149,7 @@ decode(headline, { duration: 800, settle: 'text' }); // decorative, under the 80
149
149
 
150
150
  ## 7. Performance
151
151
 
152
- - **About 12 fields per page.** At most 12 kit fields hold a WebGL context at once (`setFieldBudget` changes it). asciify.org's home page runs 12. Past that, share: one `pill()` call over a list draws every pill with one field.
152
+ - **Fields share one context.** Every field on the page draws through one WebGL2 context, so the count of fields is bounded by what is animating on screen, not by contexts. Static fields draw once and stop. Sharing still helps: one `pill()` call over a list draws every pill with one field.
153
153
  - **Lazy by default.** Fields mount within half a viewport and release beyond one. Use `lazy: false` only for the hero in the first viewport.
154
154
  - **Posters.** Give every field above the fold, and every field that matters without WebGL, a still per theme, and swap it on theme change with `update({ poster })` rather than remounting.
155
155
  - **Canvas presets cost CPU, not contexts.** Count them toward the page anyway; one animated ground per viewport.
@@ -169,8 +169,8 @@ const s = mountSurface({ host: el, look: () => ({ mark: 'glyph', cellSize: 6 }),
169
169
 
170
170
  A page can hold every kit component at once. The shared core keeps the GPU cost to what is on screen:
171
171
 
172
- - **Lazy mounting.** A field mounts when its element comes within half a viewport of the screen and is released, freeing its WebGL context, beyond one viewport. Mounts that cost more than about 4 ms in a frame wait for the next one, nearest the screen first. The handle survives release and remount: its methods keep working, `update()` calls made while released apply on the next mount, `pause()` holds, and the component paints its current state when it mounts again. `handle.field` stays the same object throughout. A remount swaps a copy of the canvas, with its attributes and inline styles, into the same place. `lazy: false` mounts at once, anywhere on the page, and is never released by distance.
173
- - **Context budget.** At most 12 kit fields hold a WebGL context at once, across all components and page helpers (browsers drop the oldest past about 16). Past the budget the live field farthest off screen is released first, and a waiting field mounts as soon as it is nearer than the farthest live one. Change it with `setFieldBudget(n)`; `getFieldBudget()` reads it.
172
+ - **Lazy mounting.** A field mounts when its element comes within half a viewport of the screen and is released, freeing its GPU memory, beyond one viewport. Mounts that cost more than about 4 ms in a frame wait for the next one, nearest the screen first. The handle survives release and remount: its methods keep working, `update()` calls made while released apply on the next mount, `pause()` holds, and the component paints its current state when it mounts again. `handle.field` stays the same object throughout. A remount draws into the same canvas (a field with a context of its own swaps in a copy, with its attributes and inline styles). `lazy: false` mounts at once, anywhere on the page, and is never released by distance.
173
+ - **One context, and a budget for the rest.** Kit fields draw through the page's one shared WebGL2 context and hold none of their own. `setFieldBudget(n)` (default 12) limits fields that fall back to a context of their own (a canvas in another document, or one already holding a WebGL context; browsers drop the oldest past about 16): past it the live one farthest off screen is released first. `getFieldBudget()` reads it.
174
174
  - **Posters.** `poster: '/stills/hero.webp'` puts an `<img>` under the canvas (`update({ poster })` points it at another still, such as the other theme's), placed exactly where the field draws. It fades out (280 ms, none under reduced motion) when the first GPU frame lands, shows again while the field is released, and stays as the fallback without WebGL or after the field fails, until `destroy()`. `update({ poster })` swaps the still in place, for a theme change, on every component including the page helpers and `asciiVideo`. `asciiVideo` keeps its own poster.
175
175
  - **Pausing.** As before, a field draws nothing while the page is hidden or its element is off screen.
176
176
 
@@ -0,0 +1,33 @@
1
+ # Performance guarantees
2
+
3
+ What `asciify-engine` promises a host page, and the default budgets. Measured by `tests/host-cost-browser.mjs` and `scripts/bundle-cost.mjs` in the engine repository, and held by its unit tests.
4
+
5
+ ## Import and bundle
6
+
7
+ - **No work at import.** Every entry (`asciify-engine`, `/core`, `/field`, `/hover`, `/studio`, `/kit`) only defines code: no DOM, canvas, WebGL, observer, timer, frame or global is touched until you call something. Safe under SSR (Next.js, Remix, Astro) without a `window` guard on the import.
8
+ - **`sideEffects: false`, and the kit is split per component.** A route that imports one kit component downloads that component, the shared kit core (about 20 kB) and the field (about 67 kB), not the whole kit: `fieldBackground` alone costs a code-split host about 90 kB minified (33 kB gzipped), against 285 kB (100 kB) when the kit was one module.
9
+
10
+ ## Main thread
11
+
12
+ - **Mounting is spread out.** Kit fields mount when they come within half a viewport of the screen, at most one per frame however many a host starts in one task (a route change, a long list). Shader programs link with `KHR_parallel_shader_compile` where the browser has it.
13
+ - **Nothing runs when nothing changes.** A still field stops its frame loop. Fields off screen, in a hidden tab, in a `display: none` host or with zero size do not draw. Reduced motion settles every component on a still.
14
+ - **Ambient motion is capped.** Drift, scenes and self-animating components draw at most 60 frames a second on any screen (30 with two cores or less, 2 GB of memory or less, or Save-Data). The pointer's wake, lantern, trail, pulses and anything under the pointer keep the screen's full rate.
15
+ - **Studio hovers stay responsive.** A live `mountStudio` hover renders at most 60 times a second and never more often than leaves half the main thread free, then ends on its settled frame.
16
+ - **Listeners are passive** where the event allows, and every listener, observer, frame, timer, object URL and context is released by `destroy()`, including after a hundred quick mount and destroy cycles.
17
+ - **No global CSS but scoped fallbacks.** The few components that inject styles use one `<style id="ascii-kit-…">` each, with every rule inside `:where()` (zero specificity). No console output in normal use.
18
+
19
+ ## GPU and memory
20
+
21
+ - **One context per page.** Every glyph field on the page (kit components, page helpers and `mountGlyphField`) draws through one shared WebGL2 context and one frame loop, and each field's canvas receives its frame with a 2D copy. Programs compile once for the page and glyph atlases are shared per charset, font and size, so mounting a field creates no context and compiles nothing; 150 components on one page hold one context. A context the browser takes back is replaced, and every field draws again. The context is let go three seconds after the page's last field.
22
+ - **GPU memory budget: 256 MB per page.** The shared context counts what every field holds on the GPU: its render targets, its source texture with mipmaps, its wake and trail buffers, its canvas, the shared atlases and the shared drawing buffer. Past 256 MB, kit fields off screen are released to their posters, farthest first, and fields waiting to mount keep their posters until there is room; a field on screen is never released for memory. Atlases are shared per charset, font and size; up to eight that no field uses are kept for a remount and evicted oldest first (all of them while over budget). The shared drawing buffer shrinks when the largest field leaves. Fields mounted directly with `mountGlyphField` count toward the total but are never released by it: destroy them yourself.
23
+ - **Context budget.** `setFieldBudget(n)` (default 12) still limits fields that fall back to a context of their own: a canvas from another document, or one that already holds a WebGL context. Past it the field farthest off screen is released to its `poster`.
24
+ - **Page listeners don't grow.** One pointer listener (capture, on the window), one visibility, motion, colour-scheme and font listener, and one ResizeObserver serve every field, so mounting and releasing fields while scrolling adds and removes none.
25
+ - **Backing store capped.** Pixel ratio is at most 2 by default (`look.maxPixelRatio`), and one field's backing store at most about one 4K screen (8.3 million pixels): a full-bleed field on a 5K or 6K display keeps its cells and draws them at a lower ratio.
26
+ - **Slow frames degrade, not stall.** The shared context times its own frames on the GPU (`EXT_disjoint_timer_query_webgl2`, or the frame loop's gaps where it is missing) and lowers the render scale in steps, down to one device pixel per CSS pixel, before any frame is given up; it climbs back once there is headroom. Past that, about a second of frames slower than 24 fps stops a field's ambient motion; the pointer response keeps working.
27
+ - **Software WebGL counts as unsupported.** SwiftShader and llvmpipe report `isGlyphFieldSupported() === false` (pass `allowSoftware` to override), so components keep their HTML and poster instead of drawing every pixel on the CPU. Without WebGL2 the same fallback applies.
28
+
29
+ ## What a host should still do
30
+
31
+ - Give heavy components a `poster`, so a released or unsupported field still shows its still.
32
+ - Call `destroy()` on unmount. In React, mount in an effect and destroy in its cleanup; StrictMode's double mount is safe.
33
+ - Keep `lazy` on (the default) for anything below the fold. `lazy: false` mounts at once wherever the element is.