asciify-engine 4.7.0 → 4.8.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.
- package/README.md +17 -0
- package/dist/studio.cjs +1 -1
- package/dist/studio.d.cts +168 -5
- package/dist/studio.d.ts +168 -5
- package/dist/studio.js +1 -1
- package/examples/layered-composition.ts +24 -0
- package/package.json +2 -1
- package/skills/asciify-engine/SKILL.md +4 -2
- package/skills/asciify-engine/agents/openai.yaml +2 -2
- package/skills/asciify-engine/references/layered-projects.md +89 -0
- package/skills/asciify-engine/references/project-integration.md +1 -0
- package/skills/asciify-engine/references/upgrading.md +4 -0
|
@@ -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).
|
|
@@ -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
|
|
|
@@ -73,3 +73,7 @@ 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.
|