asciify-engine 4.7.0 → 4.9.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.
@@ -1,8 +1,8 @@
1
1
  interface:
2
2
  display_name: "Asciify Engine"
3
3
  short_description: "Build fast ASCII media effects"
4
- brand_color: "#E8B900"
5
- default_prompt: "Use $asciify-engine to build a layered ASCII hero with readable HTML text, appropriate performance settings, and lifecycle cleanup."
4
+ brand_color: "#B3ED82"
5
+ default_prompt: "Use $asciify-engine to build interactive ASCII media or layered compositions, keeping HTML controls readable, rendering budgets explicit, and media lifecycles isolated."
6
6
 
7
7
  policy:
8
8
  allow_implicit_invocation: true
@@ -0,0 +1,89 @@
1
+ # Layered projects — 4.8+
2
+
3
+ Use `asciify-engine/studio` to combine up to eight images, videos, GIFs or FIGlet text sources on one canvas. Each layer has its own Studio settings, placement, opacity and blend mode. This is a local composition API. Keep accessible headlines, links and controls in HTML; raster text layers are artwork or export content.
4
+
5
+ ## Live component
6
+
7
+ ```ts
8
+ import { mountStudioProject, normalizeStudioProject } from 'asciify-engine/studio';
9
+ const project = normalizeStudioProject({
10
+ width: 1280, height: 720, background: '#0c1711',
11
+ layers: [
12
+ { id: 'scene', source: 'photo', name: 'Landscape', settings: {
13
+ cellSize: 6, ink: '#93b79b',
14
+ motion: { type: 'parallax', amount: .3, speed: .8 },
15
+ hover: { effect: 'trail', strength: .55, radius: .45 },
16
+ } },
17
+ { id: 'title', source: 'caption', name: 'Caption',
18
+ placement: { width: .8, height: .25 },
19
+ settings: { cellSize: 3, ink: '#b3ed82', hover: { effect: 'elastic' } },
20
+ },
21
+ ],
22
+ });
23
+ const sources = {
24
+ photo: '/your-owned-photo.webp', // or a user-selected File
25
+ caption: { text: 'HELLO', font: 'Small' as const },
26
+ };
27
+ const controller = new AbortController();
28
+ const player = await mountStudioProject(canvas, project, sources, {
29
+ maxDimension: 1280, maxCells: 24000, maxRasterPixels: 2097152,
30
+ signal: controller.signal, onError: error => showError(error.message),
31
+ });
32
+ player.updateLayer('scene', { settings: { hover: { effect: 'water' } } });
33
+ player.updateLayer('title', { placement: { y: .65 }, opacity: .8 });
34
+ // On unmount: controller.abort(); player.destroy();
35
+ ```
36
+
37
+ Do not attach `mountHover` or one RAF per layer. This player owns the combined loop, input, visibility/reduced-motion handling and loaded assets. Shared media is sampled once per frame; unchanged child canvases are cached. Ordinary settings edits do not reload assets. See the typed package example `examples/layered-composition.ts`.
38
+
39
+ ## Model and placement
40
+
41
+ `kind: 'asciify-project'`, `version: 1`; dimensions are 2–4096 pixels per side. Empty projects are valid. Layer arrays run **back to front**. Layer IDs must be unique. IDs/source keys are 1–64 letters, numbers, colons, hyphens or underscores, starting with a letter or number; names are display labels up to 80 characters.
42
+
43
+ Placement is center-based: `x/y` default `.5` (range −1…2), `width/height` default `1` (range .01…2), relative to the project canvas. Rotation is degrees −180…180; `flipX/flipY` default false. Use the layer's Studio `crop` and `warps` for source framing. Opacity is 0…1; `visible` defaults true; blending uses `BLEND_MODES`. Project background accepts a six-digit hex or `null`. Layers default to transparent backdrops; an explicit opaque layer backdrop covers artwork behind it.
44
+
45
+ `pointerMode: 'top'` targets the frontmost interactive rectangle; `'all'` targets all rectangles beneath the pointer. Rotation/flips are inverted for input. Picking is rectangular, **not alpha/glyph hit testing**: transparent holes do not pass input through. Off/zero-strength layers are skipped.
46
+
47
+ Every layer accepts the complete `StudioInput`: all 18 styles, dither, masks, curves, source warps, hover, ambient motion and finishing. Use the published catalogs; source warps, motion and hover are independent.
48
+
49
+ ## Assets and lifecycle
50
+
51
+ `StudioProjectSources` maps keys to `File`, direct CORS-compatible media URLs or `{text, font?, ink?}`. Text uses `STUDIO_TEXT_FONTS`, defaults to white on transparency and can be recolored by layer settings. YouTube page URLs are not media URLs.
52
+
53
+ Up to eight bindings are loaded, including unused supplied bindings. Every referenced source, even on a hidden layer, must be supplied. Multiple layers can share one key. To add/replace a binding, destroy the old player and mount the new dictionary. Reordering, visibility, placement and settings use `player.update(project)` or `updateLayer` without media reload. Remove unused bindings when remounting. Files stay local; URLs still make normal media requests.
54
+
55
+ `mountStudioProjectMedia` accepts a `ReadonlyMap<string, StudioMedia>` and takes ownership on successful setup. It destroys each unique asset once. If setup fails before returning, the caller retains cleanup responsibility for supplied media. `mountStudioProject` cleans up its own failed loads. Never give the same owned media instance to two players or export.
56
+
57
+ `createStudioProjectRenderer` is the lower-level alternative: no media loading, RAF or events. Supply a `ReadonlyMap<string, StudioSource>` to `render`, and `invalidate(key)` when external code changes pixels in place. Video frame stamps invalidate automatically. `render` returns whether it repainted; `configure` replaces the project. Always destroy it.
58
+
59
+ The player exposes `pause`, `seek`, `resize(width,height,pixelRatio?)`, `redraw`, `setBudget`, `setAdaptive`, `update`, `updateLayer` and idempotent `destroy`. Read detached `project` and `motionTimes` snapshots and current `time`. New seeks cancel older ones with `AbortError`; custom media `seek` must honor its AbortSignal. Native videos use independent browser playback, not frame-locked live playback. Export seeks each source deterministically.
60
+
61
+ ## Budgets and timing
62
+
63
+ One cell budget (default 24,000; range 2,048–1,048,576) is divided by layer area, with a 256-cell minimum per visible layer. One **child-output raster** budget (default 4,194,304; range 65,536–16,777,216 pixels) caps combined child output size. It does not cap decoders or all internal surfaces. Hidden, transparent and offscreen layers skip rendering; loaded assets can still occupy memory. Their native videos pause.
64
+
65
+ `quality` reports per-layer `{id,maxCells,width,height}` allocations, not achieved FPS. Adaptive mode lowers the total cell budget after sustained render cost. `maxDimension` bounds root output; `pixelRatio` describes requested raster scale and preserves logical density through downsampling. Stills stop scheduling when unchanged; motion/hover wakes them. Reduced motion, hidden documents and offscreen canvases pause playback. Multiple optical finishes or dense dither still have real cost; measure target hardware.
66
+
67
+ Do not promise seamless combined loops unless source lengths and effect periods align. Ambient cycles last 12/speed seconds; temporal grain, dust, glitch, dither and media have separate timing.
68
+
69
+ ## Save and reopen
70
+
71
+ `serializeStudioProject(player.project)` saves settings/source keys, never files or URLs. `parseStudioProject` validates kind/version, unique IDs and the eight-layer limit, with a 1 MB settings-text limit. Single-source `serializeStudioSettings` JSON is a different format. Maintain a separate asset manifest and ask for missing files; do not substitute artwork silently.
72
+
73
+ Asciify's layered editor adds optional `textSources` metadata so text is portable. The engine parser ignores app metadata. Integrations must validate/rebind that dictionary themselves or supply their own bindings. Never execute imported fields or fetch arbitrary metadata URLs.
74
+
75
+ ## Export independently
76
+
77
+ ```ts
78
+ import { exportStudioProject } from 'asciify-engine/studio';
79
+ const png = await exportStudioProject(player.project, sources, {
80
+ format: 'png', width: 1280, height: 720,
81
+ referenceWidth: canvas.width / player.pixelRatio,
82
+ maxCells: player.maxCells, time: player.time, motionTimes: player.motionTimes,
83
+ signal: exportController.signal,
84
+ });
85
+ ```
86
+
87
+ Export freezes the project/bindings/phases and loads independent media. It never seeks, pauses or destroys the preview. Visible shared sources seek once per frame. Failure/cancellation aborts sibling seeks and releases all export resources. PNG/JPEG capture specified time/phases; GIF/MP4/WebM start at zero and do not record pointer paths. Generic Studio export size/codec/GIF limits apply. WebGL-only finishes error if unavailable. Actual GPU appearance and codec playback require browser verification.
88
+
89
+ Update the runtime and installed agent skill separately: [upgrading.md](upgrading.md).
@@ -0,0 +1,63 @@
1
+ # Print and illustration (4.9)
2
+
3
+ Six local Canvas renderers live in the optional `asciify-engine/studio` entry. They do not change the default ASCII style or add a server/provider. They share source grading, curves, warps, masks, hover, still-image motion, finishing, saved settings, single-image export and layered projects.
4
+
5
+ | Style ID | Mechanism | Useful controls |
6
+ | --- | --- | --- |
7
+ | `stipple` | Four spatially jittered dots per sample; tone changes their area | Coverage, weight, texture, seed |
8
+ | `engraving` | Parallel world-anchored cuts with tone-dependent width | Coverage, weight, angle, texture |
9
+ | `crosshatch` | Up to three crossing screens appear across the tonal range | Coverage, weight, angle, texture |
10
+ | `woodblock` | Five tone steps in broad carved bands with correlated irregular edges | Coverage, weight, angle, texture |
11
+ | `risograph` | Two overlapping ink-dot screens; source warmth separates coverage in source-color mode | Second ink, registration offset/direction, texture |
12
+ | `pointillism` | Four small elliptical pigment strokes per sample | Source color, weight, texture, seed |
13
+
14
+ `STUDIO_PRINT_STYLES` supplies `value`, `label`, and `description` for selectors. `isStudioPrintStyle(style)` identifies this family. The full `STUDIO_STYLES` catalog has 24 styles. Existing `riso` and `print` look IDs remain dither presets; choose `style: 'risograph'` to use the new two-screen renderer. These are print-inspired raster treatments, not physical ink simulation, CMYK separations or vector paths.
15
+
16
+ ## Live still image
17
+
18
+ ```ts
19
+ import { mountStudio } from 'asciify-engine/studio';
20
+
21
+ const player = await mountStudio(canvas, '/your-image.jpg', {
22
+ settings: {
23
+ style: 'engraving', cellSize: 7, colorMode: 'accent', ink: '#becbbe',
24
+ print: { angle: -35, weight: 1.1, roughness: .3 },
25
+ backdrop: { color: '#101810' },
26
+ motion: { type: 'sheen', speed: .5, amount: .4 },
27
+ hover: { effect: 'water', radius: .4, strength: .5 },
28
+ },
29
+ maxDimension: 960, maxCells: 12000,
30
+ onError: console.error,
31
+ });
32
+ player.update({ print: { angle: 20 } }); // retains the other print settings
33
+ const settings = player.settings; // detached snapshot for saving/export
34
+ // On unmount:
35
+ player.destroy();
36
+ ```
37
+
38
+ For dark ink on paper, use `print.invert: true`, a dark `ink`, and a light `backdrop.color`. Inversion chooses which source tones receive ink; it does not change the ink or backdrop automatically. Dissolve reduces ink in either polarity. Trail still inverts the local tonal response. The motion engine retains its own `12 / speed` cycle; the print texture has no independent time source.
39
+
40
+ ## Exact settings
41
+
42
+ `print` is an optional nested group in `StudioInput` and saved version-1 `StudioSettings`. Old files receive compatible defaults. Partial updates merge this group. `normalizeStudioPrint`, `DEFAULT_STUDIO_PRINT`, and `StudioPrintSettings` are exported from `/studio`.
43
+
44
+ | Field | Range / default | Meaning |
45
+ | --- | --- | --- |
46
+ | `density` | 0–2 / 1 | Tonal coverage; zero draws no print marks |
47
+ | `weight` | .25–2 / 1 | Dot/stroke size; size is bounded within the screen |
48
+ | `angle` | −180–180° / −35 | Line angle for engraving/crosshatch/woodblock; registration direction for risograph |
49
+ | `roughness` | 0–1 / .35 | Spatial texture variation; not random animation |
50
+ | `invert` | boolean / false | False draws highlights; true draws shadows |
51
+ | `secondaryInk` | six-digit hex / `#ec6752` | Second risograph ink only |
52
+ | `registration` | 0–1 / .2 | Risograph offset, up to .38 of one cell |
53
+ | `seed` | integer 0–65535 / 7 | Repeatable spatial texture |
54
+
55
+ All print styles use square sampling cells. `cellSize` accepts 1–60 in the reference frame; `studioGrid` still reports any effective budget limit. It is independent of `dither.scale`. Source color and gray modes remain available. Only Pointillism's source-color mode separates pigment hues; accent mode retains the chosen ink. Risograph mixes its two screens on transparent artwork with multiply, then composes that artwork over the chosen backdrop.
56
+
57
+ ## Work and quality limits
58
+
59
+ Compiled screen directions and texture buffers are reused. No per-cell canvas, save/rotate/clip, or new simulation loop is created. A stable still caches the complete composition. Spatial texture uses a deterministic seed and bounded lookup; source tone changes can still change coverage. Woodblock intentionally has stepped tones. Do not label it smooth continuous shading.
60
+
61
+ Dense live print rendering costs more than the sample count alone: Pointillism draws four strokes per cell, Crosshatch up to three screens, and Woodblock emits carved polygons. Start with 12,000 cells, retain adaptive quality for motion, and measure the real device. High-resolution still exports can use a larger explicit budget. Keep the same reference pixel ratio/grid for preview and export.
62
+
63
+ The regression suite checks geometry, normalization, scaling, deterministic texture, composition caching and every hover/motion path. CPU geometry timings exclude actual Canvas drawing, decoding, GPU work and browser frame pacing. Visual output, browser export and mobile frame-rate acceptance are still unverified; do not claim competitor parity or guaranteed FPS from these tests.
@@ -7,6 +7,7 @@ A downloaded PNG is one output; it is not a substitute for a live component.
7
7
  | --- | --- | --- |
8
8
  | Image/video/GIF hero with HTML titles and text cutouts | `/core` media API + optional `/hover` | Disable core hover/motion when `/hover` owns them. |
9
9
  | Art-style editor, live still image, saved look, backdrop, masks, PNG/video export | `/studio` → `mountStudio` | Studio owns motion, hover and finishing. Do not stack `mountHover` on it. |
10
+ | Multiple media/text layers with independent styles, placement, blends and exports | `/studio` → `mountStudioProject` (4.8+) | One shared loop and budget; see [layered-projects.md](layered-projects.md). |
10
11
  | Application-owned render loop | `/studio` → `createStudioRenderer` | Caller passes time, pointer input, invalidation and cleanup. |
11
12
  | ASCII text data | Core frame/text conversion | No animation loop needed unless requested. |
12
13
 
@@ -25,7 +25,7 @@ await studio.seek(1.5);
25
25
  studio.destroy();
26
26
  ```
27
27
 
28
- Keep complete settings in application state. Apply edits with `updateStudioSettings(settings, patch)` and pass that state to `studio.update`; the mounted instance does not expose a settings getter. Serialize your application state when saving a look.
28
+ Keep complete settings in application state. Apply edits with `updateStudioSettings(settings, patch)` and pass that state to `studio.update`; from 4.6 the mounted instance also exposes a detached `settings` snapshot. Serialize that snapshot or your application state when saving a look.
29
29
 
30
30
  In React, create the AbortController inside the effect. On cleanup, abort and destroy the returned instance; if setup resolves after disposal, immediately destroy that instance. Do not remount for slider changes: call `update`. The mount owns its loaded media. `mountStudioMedia` takes ownership of an already loaded `StudioMedia`. Use an independent media instance for export.
31
31
 
@@ -190,7 +190,7 @@ Example: `player.update({ motion: { type: 'trace', speed: 1, amount: .7 }, hover
190
190
 
191
191
  Use `color.gamma` to lift midtones (above 1) or deepen them (below 1), from 0.25 to 4. `color.shadows` and `color.highlights` range from −1 to 1; positive lifts that region, negative darkens it. They form a monotone curve with fixed black, middle and white anchors. This is a bounded tonal curve, not a freely editable per-channel curve graph. It cannot recover clipped source detail.
192
192
 
193
- `color.blackPoint` (0–0.99, default 0) and `color.whitePoint` (default 1) map the input range before gamma. White is normalized to at least 1/255 above black. These controls apply equally to all 18 renderers before dither/character conversion, preserve source alpha, and round-trip in settings. Neutral settings skip the transform. The 256-entry lookup is rebuilt on configuration changes and reused for source frames. Reset with `{ blackPoint: 0, whitePoint: 1, gamma: 1, shadows: 0, highlights: 0 }`.
193
+ `color.blackPoint` (0–0.99, default 0) and `color.whitePoint` (default 1) map the input range before gamma. White is normalized to at least 1/255 above black. These controls apply equally to all Studio renderers before dither/character conversion, preserve source alpha, and round-trip in settings. Neutral settings skip the transform. The 256-entry lookup is rebuilt on configuration changes and reused for source frames. Reset with `{ blackPoint: 0, whitePoint: 1, gamma: 1, shadows: 0, highlights: 0 }`.
194
194
 
195
195
  `blue-noise` uses a deterministic 64×64 toroidal rank tile generated offline with void-and-cluster relaxation. It suppresses low-frequency clumping; it is not white noise with a different label. A palette-pair projection controls coverage, preserving flat grayscale means for a black/white palette at full strength. Arbitrary color palettes still approximate the source. The original palette uses web-safe channel quantization. The rank tile is 8 KiB in memory, loads only with `/studio`, and has no network request or runtime generator.
196
196
 
@@ -243,3 +243,7 @@ const saved = serializeStudioSettings(player.settings);
243
243
  ## Ordered source shaping (4.7)
244
244
 
245
245
  `warps` stores up to eight optional transforms applied in array order before all styles. `warpEdge` selects extended or transparent edges. Read [source shaping](source-shaping.md) for the nine mechanisms, controls, normalization, editor construction and dense-video cost. This is separate from art-style selection, pointer response and autonomous motion. The default stack is empty.
246
+
247
+ ## Print and illustration (4.9)
248
+
249
+ The catalog adds six print treatments with fine square cells and an optional `print` settings group. See [print-styles.md](print-styles.md) for the mechanisms, controls, polarity, bounded rendering and living-image example. They use the same Studio pipeline, saved settings and exports.
@@ -73,3 +73,11 @@ Adds Studio `color.curves` and `color.denoise` with neutral defaults. Curves sup
73
73
  ### 4.7 ordered source warps
74
74
 
75
75
  Adds optional Studio `warps` and `warpEdge` with an empty default stack. Nine transform types are exposed through `STUDIO_WARPS`, plus `MAX_STUDIO_WARPS` and `normalizeStudioWarps` for control builders. Updating `warps` replaces the array. Reuse `player.settings` for save/export. Static shaping does not enable motion; existing motion and hover remain independent. Dither now interpolates alpha with color during motion/hover. Settings version stays 1, root/core/hover behavior remains unchanged, and older packages ignore the new fields. See [source shaping](source-shaping.md); update the runtime and installed skill separately.
76
+
77
+ ### 4.8 layered projects
78
+
79
+ Adds `mountStudioProject`, `mountStudioProjectMedia`, `createStudioProjectRenderer`, `exportStudioProject` and the typed project model in optional `/studio`. Up to eight independently styled media/text layers share a playback loop and cell/raster budgets. Existing single-source APIs/defaults remain. Layered project JSON uses a distinct `kind` and source keys; it is not interchangeable with single-source settings JSON and does not contain media. See [layered-projects.md](layered-projects.md) for ownership, pointer routing, export consistency and migration choices. Refresh this skill separately from npm; do not migrate simple heroes unnecessarily.
80
+
81
+ ## 4.9 print rendering
82
+
83
+ Six additional `STUDIO_STYLES` use optional `print` settings. Existing v1 settings remain valid and ASCII defaults stay unchanged. The print group merges on partial updates. Import the shared catalog and read [print-styles.md](print-styles.md); offer print-specific controls only for those styles. Existing `print` and `riso` look IDs retain their dither behavior. Refresh the npm dependency and installed skill separately.