pixi-effects 0.42.0 → 0.44.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/CHANGELOG.md +17 -0
- package/README.md +5 -5
- package/ai/chat-template.html +3 -3
- package/ai/tools/check.mjs +131 -2
- package/dist/Controller.d.cts +2 -2
- package/dist/Controller.d.ts +2 -2
- package/dist/{Movie-BHGhg-fJ.d.cts → Movie-B3VOAgwc.d.cts} +5 -3
- package/dist/{Movie-CBVFZ1kg.d.ts → Movie-CQCVUqRs.d.ts} +5 -3
- package/dist/{chunk-NFJKBWYO.js → chunk-4MBTYREC.js} +100 -31
- package/dist/chunk-4MBTYREC.js.map +1 -0
- package/dist/{chunk-WVZLAIKK.cjs → chunk-MAAM45ZX.cjs} +99 -30
- package/dist/chunk-MAAM45ZX.cjs.map +1 -0
- package/dist/index.cjs +25 -25
- package/dist/index.d.cts +3 -4
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1 -1
- package/dist/loader.css +1 -1
- package/dist/present.cjs +1 -1
- package/dist/present.d.cts +2 -2
- package/dist/present.d.ts +2 -2
- package/dist/present.js +1 -1
- package/dist/three.cjs +166 -38
- package/dist/three.cjs.map +1 -1
- package/dist/three.d.cts +37 -2
- package/dist/three.d.ts +37 -2
- package/dist/three.js +168 -40
- package/dist/three.js.map +1 -1
- package/dist/{types-DIh93lFb.d.cts → types-C5xo0fzb.d.cts} +92 -1
- package/dist/{types-DIh93lFb.d.ts → types-C5xo0fzb.d.ts} +92 -1
- package/docs/api.md +4 -2
- package/docs/dsl.md +4 -2
- package/llms-full.txt +12 -8
- package/package.json +2 -1
- package/skills/pixi-effects/SKILL.md +2 -2
- package/skills/pixi-effects/reference/recipes.md +2 -2
- package/skills/pixi-effects/template.html +6 -6
- package/dist/Base-DQWIJ6bJ.d.cts +0 -60
- package/dist/Base-Jgkud1Aw.d.ts +0 -60
- package/dist/chunk-NFJKBWYO.js.map +0 -1
- package/dist/chunk-WVZLAIKK.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.44.0
|
|
4
|
+
|
|
5
|
+
An AI can now click, hover and drag on a page it made and see what happened (`check --do`), a text layer can be picked by its letters, a rebuild keeps a `three` layer that did not change, and the Interactive page has three samples that click, hover and drag. Nothing breaks.
|
|
6
|
+
|
|
7
|
+
- **New: `setVars` / `resize` / `rebuild` keep a `three` layer that did not change.** A rebuild built every layer again, so a text variable changed with a model in the scene loaded the model again (and made a renderer again). A named `three` layer with the same `setup` function, size and options now takes over the old layer's scene, renderer, texture and mirrored lights: `setup` runs once (15 ms → 1 ms for a rebuild with a box scene on WebGL, 7 → 1 on WebGPU; more with a model). The new tree is built while the old one plays, the new layer only borrows until the swap, so a rebuild that fails changes nothing and the old layer is disposed once. `reuse: false` opts out (a `setup` that reads `movie.vars`). `tests/films/three-reuse`, `tests/three/ThreeSequence.reuse.test.ts`.
|
|
8
|
+
- **New: `check --do "click Lime; hover 640,360; drag card > 770,240; wait 1; key Space"` clicks, hovers, drags and presses keys on the page.** Real mouse and key events (the page's own handlers run), after everything else; a point is `x,y` in canvas pixels or a layer name. The report says per step what was under the pointer (`over Lime/shape#0`), which variables changed (`score 0→1`), whether the page warned, and keeps a picture of the canvas after each step in `steps/`. A layer that is not there, a point outside the picture, or a console warning during a step is a problem. Until now `check` could not click and every page had to be tested by hand over Chrome's protocol. `tests/tools/checkDo.test.ts` (a test page, `tests/films/check-do.html`).
|
|
9
|
+
- **New: `movie.pick(x, y, { alpha })` looks at the letters of a text layer.** A text layer was its box (the gap between letters and the hole of an O were hits); with `alpha` it is its glyphs, including the pieces of a `spans` line and an outline. The letters are drawn again with PixiJS's own generator once per text and style. `tests/films/pick-text`: over 95 % agreement with the picture on three texts.
|
|
10
|
+
- **New: three samples on the Interactive page that click, hover and drag.** `click.html` (pop the bubbles: `movie.hover` and `movie.pick` on layers of the film), `drop.html` (drag cards onto zones: `pick` and variables), `move3d.html` (drag three.js objects along a floor under the film's own light: `hit.three.grab('floor')`, `movie.redraw()`). Until now the page only showed variables. `tests/site/interactiveClicks.test.ts` drives each with real mouse events in Chrome.
|
|
11
|
+
- **Docs: what a `pick` hit names.** The hit is the layer drawn at the point, not its group: the group is the first segment of `hit.path` (found while writing the samples: `hit.name` of a bubble made of a group and an unnamed circle was undefined).
|
|
12
|
+
|
|
13
|
+
## 0.43.0
|
|
14
|
+
|
|
15
|
+
A `three` layer on WebGPU shares PixiJS's GPU device, and the tests stop depending on the gallery. Nothing breaks.
|
|
16
|
+
|
|
17
|
+
- **Changed (tests, nothing for users): no library test uses a gallery piece as its subject.** The tests whose subject is the gallery or the site are in `tests/site/` (`npm run test:site`); the pages made for the tests, formerly `examples/_checks/`, are in `tests/films/`; the player and the timeline viewer have films of their own; `noFalseWarnings` runs every test film.
|
|
18
|
+
- **New: a `three` layer with `renderer: 'webgpu'` draws on PixiJS's own GPU device (`shareGpu`, default `true`).** In a movie PixiJS draws on WebGPU, the layer renders into a texture that PixiJS samples directly: no canvas of its own, no copy. The picture is the same (0 of 255 different in every channel of nine pictures, with antialias, fog and tone mapping), it is faster (spike: 0.7 against 1.1 ms for one layer, 1.6 against 5.7 ms for ten), and the browser's limit of about 32 WebGPU canvases no longer applies (a 60-layer film draws every layer; such a layer is not counted by the "too many three layers" warning). `shareGpu: false` keeps a canvas of its own; PixiJS on WebGL or any failure of the shared route does the same (one warning, before `setup`). `movie.inspect()` shows `sharedGpu`. The default `renderer` stays `'webgl'`. `tests/films/three-renderers`, `three-budget`, `tests/core/gpuShare.test.ts`.
|
|
19
|
+
|
|
3
20
|
## 0.42.0
|
|
4
21
|
|
|
5
22
|
Interaction on top of `pick`: hover, dragging, a hit that looks at the picture, and a fix for a false warning that every rebuilt page with a `three` layer showed since 0.40.0. Nothing breaks.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pixi-effects
|
|
2
2
|
|
|
3
|
-
> **Status**: experimental — current release `0.
|
|
3
|
+
> **Status**: experimental — current release `0.44.0`. The API may still change between minor versions; see the [CHANGELOG](./CHANGELOG.md) for what each release changed.
|
|
4
4
|
|
|
5
5
|
**[Gallery →](https://yjmtmtk.github.io/pixi-effects/examples/gallery/)** · 43 pieces written as plain data by AI models · **[For AI →](https://yjmtmtk.github.io/pixi-effects/for-ai.html)** · what an AI reads and runs · **[Playground →](https://yjmtmtk.github.io/pixi-effects/examples/playground.html)** in the browser.
|
|
6
6
|
|
|
@@ -49,8 +49,8 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
|
|
|
49
49
|
"imports": {
|
|
50
50
|
"pixi.js": "https://esm.sh/pixi.js@8.22.0?bundle-deps",
|
|
51
51
|
"mediabunny": "https://esm.sh/mediabunny",
|
|
52
|
-
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
53
|
-
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
52
|
+
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.44.0/dist/index.js",
|
|
53
|
+
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.44.0/dist/Controller.js"
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
</script>
|
|
@@ -61,7 +61,7 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
|
|
|
61
61
|
</script>
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.
|
|
64
|
+
Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.44.0/dist/index.js`) rather than a CDN-rebundled build such as `esm.sh/pixi-effects` or jsDelivr's `+esm`: the entries (`pixi-effects`, `…/controller`, `…/three`) share internal chunks, which only works when each file is served untouched.
|
|
65
65
|
|
|
66
66
|
> **Using three.js?** Add two more entries (`three` and `pixi-effects/three`) to this importmap — see [Adding three.js](#adding-threejs-optional) below.
|
|
67
67
|
>
|
|
@@ -78,7 +78,7 @@ Add two more entries to the importmap above: three.js itself, and the `pixi-effe
|
|
|
78
78
|
"...": "(everything from the importmap above)",
|
|
79
79
|
"three": "https://esm.sh/three@0.178.0",
|
|
80
80
|
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.178.0/examples/jsm/",
|
|
81
|
-
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
81
|
+
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.44.0/dist/three.js"
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
</script>
|
package/ai/chat-template.html
CHANGED
|
@@ -59,9 +59,9 @@
|
|
|
59
59
|
"imports": {
|
|
60
60
|
"pixi.js": "https://cdn.jsdelivr.net/npm/pixi.js@8.22.0/dist/pixi.min.mjs",
|
|
61
61
|
"mediabunny": "https://cdn.jsdelivr.net/npm/mediabunny@1.61.3/+esm",
|
|
62
|
-
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
63
|
-
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
64
|
-
"pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
62
|
+
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.44.0/dist/index.js",
|
|
63
|
+
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.44.0/dist/Controller.js",
|
|
64
|
+
"pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.44.0/dist/present.js"
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
</script>
|
package/ai/tools/check.mjs
CHANGED
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
* --query "a=1&b=2" (added to the page address: for a page that reads it, such as one video of a batch) ·
|
|
18
18
|
* --crop X,Y,W,H (with --at: each picture is that part of the canvas, in canvas pixels, enlarged up to 4x so a small figure can be judged) ·
|
|
19
19
|
* --pick X,Y (with --at, repeatable: what is under that canvas point at each moment, the top layer first, and the object a three layer met: `movie.pick`) ·
|
|
20
|
+
|
|
21
|
+
* --do "click Lime; hover 640,360; drag card > 770,240; wait 1; key Space" (real mouse and key events on the page, one step after the other, after everything else: what was under the pointer, which
|
|
22
|
+
* variables changed, whether the page warned, one picture per step in steps/; a point is x,y in canvas pixels or a layer name (the middle of its box); repeatable) ·
|
|
20
23
|
* --onion A:B (one picture of the movement between A and B seconds, frames overlaid: onion.png) ·
|
|
21
24
|
* --seam (a movie made to loop: the last frame to the first is compared with the steps between ordinary neighbouring frames; a jump is a problem) ·
|
|
22
25
|
* --strict (text overlaps and stops where the picture is still changing fail the check; by default they are only listed for review) · --out DIR · --frames N (contact sheet tiles, default 12) · --formats mp4,webm (default mp4) · --no-export ·
|
|
@@ -82,10 +85,41 @@ export function findChrome(env = process.env, exists = fs.existsSync, platform =
|
|
|
82
85
|
return candidates.find(c => c && exists(c)) ?? null;
|
|
83
86
|
}
|
|
84
87
|
|
|
85
|
-
export const USAGE = 'usage: node ai/tools/check.mjs <page.html> [--out DIR] [--frames N] [--formats mp4,webm] [--no-export] [--draft] [--at 3.5,title@end] [--crop X,Y,W,H] [--pick X,Y] [--onion 1:3] [--seam] [--query a=1&b=2] [--var name=value] [--size 1920x1080,1080x1920] [--timeout S] [--root DIR] [--chrome PATH] [--strict] [--help]\n(the header of ai/tools/check.mjs explains each option)';
|
|
88
|
+
export const USAGE = 'usage: node ai/tools/check.mjs <page.html> [--out DIR] [--frames N] [--formats mp4,webm] [--no-export] [--draft] [--at 3.5,title@end] [--crop X,Y,W,H] [--pick X,Y] [--do "click Lime; drag card > 770,240"] [--onion 1:3] [--seam] [--query a=1&b=2] [--var name=value] [--size 1920x1080,1080x1920] [--timeout S] [--root DIR] [--chrome PATH] [--strict] [--help]\n(the header of ai/tools/check.mjs explains each option)';
|
|
89
|
+
|
|
90
|
+
const DO_VERBS = ['click', 'hover', 'drag', 'wait', 'key'];
|
|
91
|
+
const pointOf = (text) => { const m = text.trim().match(/^(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)$/); return m ? { x: Number(m[1]), y: Number(m[2]) } : { name: text.trim() }; };
|
|
92
|
+
/**
|
|
93
|
+
* `--do` steps: "click 640,360; hover Lime; drag card > 770,240; wait 0.5; key Space" (`;` or a new line between steps). A point is `x,y` in canvas pixels or a layer name; the points of a
|
|
94
|
+
* drag are separated by `>` or by spaces (a name with spaces needs `>`). Returns [{ verb, points?, seconds?, key? }].
|
|
95
|
+
*/
|
|
96
|
+
export function parseDo(text) {
|
|
97
|
+
const steps = [];
|
|
98
|
+
for (const raw of String(text).split(/[;\n]/).map(s => s.trim()).filter(Boolean)) {
|
|
99
|
+
const [verb, ...rest] = raw.split(/\s+/);
|
|
100
|
+
const arg = rest.join(' ');
|
|
101
|
+
if (!DO_VERBS.includes(verb)) throw new Error(`--do: "${verb}" is not a step; the steps are ${DO_VERBS.join(', ')} (for example --do "click 640,360; drag card > 770,240")`);
|
|
102
|
+
if (verb === 'wait') {
|
|
103
|
+
if (!arg || !Number.isFinite(Number(arg)) || Number(arg) < 0) throw new Error(`--do: wait needs a number of seconds, got "${arg}"`);
|
|
104
|
+
steps.push({ verb, seconds: Number(arg) });
|
|
105
|
+
} else if (verb === 'key') {
|
|
106
|
+
if (!arg) throw new Error('--do: key needs a key name (Space, Enter, ArrowLeft, a, …)');
|
|
107
|
+
steps.push({ verb, key: arg });
|
|
108
|
+
} else if (verb === 'drag') {
|
|
109
|
+
const parts = arg.includes('>') ? arg.split('>') : arg.split(/\s+/);
|
|
110
|
+
const points = parts.map(p => p.trim()).filter(Boolean).map(pointOf);
|
|
111
|
+
if (points.length < 2) throw new Error(`--do: drag needs two points or more (from > to), got "${arg}"`);
|
|
112
|
+
steps.push({ verb, points });
|
|
113
|
+
} else {
|
|
114
|
+
if (!arg) throw new Error(`--do: ${verb} needs a point (x,y in canvas pixels, or a layer name), for example "${verb} 640,360"`);
|
|
115
|
+
steps.push({ verb, points: [pointOf(arg)] });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return steps;
|
|
119
|
+
}
|
|
86
120
|
|
|
87
121
|
export function parseArgs(argv) {
|
|
88
|
-
const o = { page: null, out: null, frames: 12, formats: ['mp4'], export: true, timeout: 240, root: null, chrome: null, strict: false, at: null, crop: null, draft: false, onion: null, seam: false, query: null, pick: [], vars: [], sizes: [], help: false };
|
|
122
|
+
const o = { page: null, out: null, frames: 12, formats: ['mp4'], export: true, timeout: 240, root: null, chrome: null, strict: false, at: null, crop: null, draft: false, onion: null, seam: false, query: null, pick: [], do: [], vars: [], sizes: [], help: false };
|
|
89
123
|
const need = (i, name) => { if (i + 1 >= argv.length) throw new Error(`${name} needs a value`); return argv[i + 1]; };
|
|
90
124
|
for (let i = 0; i < argv.length; i++) {
|
|
91
125
|
const a = argv[i];
|
|
@@ -108,6 +142,7 @@ export function parseArgs(argv) {
|
|
|
108
142
|
if (m.length !== 2 || !m.every(Number.isFinite)) throw new Error(`--pick must look like 640,360 (x, y in canvas pixels), got "${argv[i]}"`);
|
|
109
143
|
o.pick.push({ x: m[0], y: m[1] });
|
|
110
144
|
}
|
|
145
|
+
else if (a === '--do') o.do.push(...parseDo(need(i++, a)));
|
|
111
146
|
else if (a === '--var') o.vars.push(parseVar(need(i++, a)));
|
|
112
147
|
else if (a === '--size') o.sizes.push(...parseSizes(need(i++, a)));
|
|
113
148
|
else if (a === '--seam') o.seam = true;
|
|
@@ -620,6 +655,15 @@ export async function runCheck(opts, log = console.log) {
|
|
|
620
655
|
report.logs.push(...fresh);
|
|
621
656
|
report.problems.push(`${fresh.length} more console warning(s) / error(s) while the page was built at the other size(s) — each says what to change`);
|
|
622
657
|
}
|
|
658
|
+
// --do: real clicks, hovers, drags and keys on the page, last (they change what the page shows and remembers)
|
|
659
|
+
if (opts.do.length) {
|
|
660
|
+
report.steps = await runSteps(cdp, opts.do, outDir, { width: info.width, height: info.height }, consoleLines, report.logs);
|
|
661
|
+
report.files.steps = shown(path.join(outDir, 'steps'));
|
|
662
|
+
report.steps.forEach((st, i) => {
|
|
663
|
+
if (st.error) report.problems.push(`step ${i + 1} (${st.label}): ${st.error}`);
|
|
664
|
+
if (st.warnings?.length) report.problems.push(`step ${i + 1} (${st.label}): ${st.warnings.length} console warning(s) — ${st.warnings[0].slice(0, 200)}`);
|
|
665
|
+
});
|
|
666
|
+
}
|
|
623
667
|
return finish(report, outDir, log);
|
|
624
668
|
} finally {
|
|
625
669
|
try { chromeProc?.kill(); } catch { /* already gone */ }
|
|
@@ -673,6 +717,85 @@ export function exportLine(ex) {
|
|
|
673
717
|
return ` export ${ex.format}${ex.draft ? ' (draft)' : ''} ok · ${kb(ex.bytes)}${rate} · ${v} · ${au}`;
|
|
674
718
|
}
|
|
675
719
|
|
|
720
|
+
const KEYS = { Space: [' ', 'Space'], Enter: ['Enter', 'Enter'], Escape: ['Escape', 'Escape'], Tab: ['Tab', 'Tab'], Backspace: ['Backspace', 'Backspace'], ArrowLeft: ['ArrowLeft', 'ArrowLeft'], ArrowRight: ['ArrowRight', 'ArrowRight'], ArrowUp: ['ArrowUp', 'ArrowUp'], ArrowDown: ['ArrowDown', 'ArrowDown'] };
|
|
721
|
+
const keyEvent = (name) => KEYS[name] ? { key: KEYS[name][0], code: KEYS[name][1] } : { key: name, code: /^[a-z]$/i.test(name) ? `Key${name.toUpperCase()}` : /^\d$/.test(name) ? `Digit${name}` : name };
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* `check --do`: the steps, run on the page with real mouse and key events (the page's own handlers run: nothing is simulated), one picture of the canvas after each, in `steps/`.
|
|
725
|
+
* Returns [{ verb, label, hits, vars, frame, playing, warnings, picture, error? }]: what was under the last point, the variables that changed ([before, after]), what the page warned.
|
|
726
|
+
*/
|
|
727
|
+
export async function runSteps(cdp, steps, outDir, size, consoleLines, seenLogs) {
|
|
728
|
+
const dir = path.join(outDir, 'steps');
|
|
729
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
730
|
+
for (const old of fs.readdirSync(dir)) if (old.endsWith('.png')) fs.rmSync(path.join(dir, old));
|
|
731
|
+
await cdp.send('Emulation.setDeviceMetricsOverride', { width: 1400, height: 1000, deviceScaleFactor: 1, mobile: false });
|
|
732
|
+
await cdp.eval(`movie.app.canvas.scrollIntoView({ block: 'center', inline: 'center' })`);
|
|
733
|
+
await sleep(150);
|
|
734
|
+
const seen = new Set(seenLogs);
|
|
735
|
+
const results = [];
|
|
736
|
+
const pageLogs = async () => [...new Set([...(await cdp.eval('window.__logs || []').catch(() => [])), ...consoleLines])];
|
|
737
|
+
const geometry = () => cdp.eval(`(() => { const r = movie.app.canvas.getBoundingClientRect(); return { left: r.left, top: r.top, w: r.width, h: r.height }; })()`);
|
|
738
|
+
const mouse = async (g, p, type, extra = {}) => cdp.send('Input.dispatchMouseEvent', { type, x: g.left + p.x * g.w / size.width, y: g.top + p.y * g.h / size.height, ...extra });
|
|
739
|
+
let n = 0;
|
|
740
|
+
for (const step of steps) {
|
|
741
|
+
n++;
|
|
742
|
+
const res = { verb: step.verb, label: step.verb + (step.points ? ' ' + step.points.map(pt => pt.name ?? `${pt.x},${pt.y}`).join(' > ') : '') }; // (a name that is found gets its place added below)
|
|
743
|
+
const before = await cdp.eval('JSON.parse(JSON.stringify(movie.vars || {}))').catch(() => ({}));
|
|
744
|
+
let last = null;
|
|
745
|
+
try {
|
|
746
|
+
if (step.verb === 'wait') { res.label = `wait ${step.seconds}`; await sleep(step.seconds * 1000); }
|
|
747
|
+
else if (step.verb === 'key') {
|
|
748
|
+
res.label = `key ${step.key}`;
|
|
749
|
+
const e = keyEvent(step.key);
|
|
750
|
+
await cdp.send('Input.dispatchKeyEvent', { type: 'keyDown', ...e, text: e.key.length === 1 ? e.key : undefined });
|
|
751
|
+
await cdp.send('Input.dispatchKeyEvent', { type: 'keyUp', ...e });
|
|
752
|
+
} else {
|
|
753
|
+
const points = [];
|
|
754
|
+
for (const pt of step.points) {
|
|
755
|
+
if (pt.name === undefined) { points.push({ ...pt }); continue; }
|
|
756
|
+
const box = await cdp.eval(`(async () => { const r = (await movie.inspect(movie.currentFrame)).layers.find(l => l.name === ${JSON.stringify(pt.name)} || l.path === ${JSON.stringify(pt.name)}); return r && r.bounds ? { x: r.bounds.x + r.bounds.width / 2, y: r.bounds.y + r.bounds.height / 2 } : null; })()`);
|
|
757
|
+
if (!box) throw new Error(`no layer named "${pt.name}" at this moment (it may not be alive yet: put a wait before, or give x,y)`);
|
|
758
|
+
points.push({ x: Math.round(box.x), y: Math.round(box.y), name: pt.name });
|
|
759
|
+
}
|
|
760
|
+
res.label = `${step.verb} ${points.map(pt => pt.name ? `${pt.name} (${pt.x}, ${pt.y})` : `${pt.x},${pt.y}`).join(' > ')}`;
|
|
761
|
+
res.points = points.map(({ x, y }) => ({ x, y }));
|
|
762
|
+
for (const pt of points) if (!(pt.x >= 0 && pt.x <= size.width && pt.y >= 0 && pt.y <= size.height)) throw new Error(`the point ${pt.x},${pt.y} is outside the picture (0..${size.width} × 0..${size.height})`);
|
|
763
|
+
const g = await geometry();
|
|
764
|
+
last = points[points.length - 1];
|
|
765
|
+
if (step.verb === 'hover') await mouse(g, points[0], 'mouseMoved');
|
|
766
|
+
else if (step.verb === 'click') {
|
|
767
|
+
await mouse(g, points[0], 'mouseMoved'); await sleep(60);
|
|
768
|
+
await mouse(g, points[0], 'mousePressed', { button: 'left', clickCount: 1 }); await sleep(40);
|
|
769
|
+
await mouse(g, points[0], 'mouseReleased', { button: 'left', clickCount: 1 });
|
|
770
|
+
} else { // drag: press at the first point, move through the others in small steps, release at the last
|
|
771
|
+
await mouse(g, points[0], 'mouseMoved'); await sleep(60);
|
|
772
|
+
await mouse(g, points[0], 'mousePressed', { button: 'left', clickCount: 1, buttons: 1 }); await sleep(60);
|
|
773
|
+
for (let i = 1; i < points.length; i++) for (let k = 1; k <= 8; k++) {
|
|
774
|
+
const a = points[i - 1], b = points[i];
|
|
775
|
+
await mouse(g, { x: a.x + (b.x - a.x) * k / 8, y: a.y + (b.y - a.y) * k / 8 }, 'mouseMoved', { buttons: 1 }); await sleep(25);
|
|
776
|
+
}
|
|
777
|
+
await mouse(g, last, 'mouseReleased', { button: 'left', clickCount: 1 });
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
} catch (e) { res.error = e.message; }
|
|
781
|
+
await sleep(300); // the page's handlers and rebuilds settle
|
|
782
|
+
const after = await cdp.eval('JSON.parse(JSON.stringify(movie.vars || {}))').catch(() => ({}));
|
|
783
|
+
res.vars = Object.fromEntries(Object.keys({ ...before, ...after }).filter(k => JSON.stringify(before[k]) !== JSON.stringify(after[k])).map(k => [k, [before[k], after[k]]]));
|
|
784
|
+
if (last) res.hits = await cdp.eval(`movie.pickAll(${last.x}, ${last.y}).map(h => h.kind === 'three' ? h.path + ' (three: ' + (h.three && h.three.object || '?') + ')' : h.path)`).catch(() => []);
|
|
785
|
+
res.frame = await cdp.eval('movie.currentFrame').catch(() => null);
|
|
786
|
+
res.playing = await cdp.eval('!!movie.isPlaying').catch(() => null);
|
|
787
|
+
const now = await pageLogs();
|
|
788
|
+
res.warnings = now.filter(l => !seen.has(l));
|
|
789
|
+
for (const l of res.warnings) seen.add(l);
|
|
790
|
+
const g = await geometry();
|
|
791
|
+
const shot = await cdp.send('Page.captureScreenshot', { format: 'png', clip: { x: g.left, y: g.top, width: g.w, height: g.h, scale: 1 } }).catch(() => null);
|
|
792
|
+
if (shot?.data) { const file = path.join(dir, `${String(n).padStart(2, '0')}-${step.verb}.png`); fs.writeFileSync(file, Buffer.from(shot.data, 'base64')); res.picture = shown(file); }
|
|
793
|
+
results.push(res);
|
|
794
|
+
}
|
|
795
|
+
return results;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const fmtVar = (v) => typeof v === 'string' ? JSON.stringify(v.length > 24 ? v.slice(0, 21) + '…' : v) : String(v);
|
|
676
799
|
function finish(report, outDir, log) {
|
|
677
800
|
const reportFile = path.join(outDir, 'report.json');
|
|
678
801
|
report.files.report = shown(reportFile);
|
|
@@ -727,6 +850,12 @@ function finish(report, outDir, log) {
|
|
|
727
850
|
const shown = Object.entries(w.light).sort((a, b) => b[1] - a[1]).slice(0, 8).map(([k, v]) => `${k} ${v}`).join(', '); // the most lit first
|
|
728
851
|
L.push(` light ${w.label} — ${shown}${Object.keys(w.light).length > 8 ? ', …' : ''} (the most lit threeD layers first: 1 = as bright as with no light, 0 = black; the number is the layer's middle, not what you can see)`);
|
|
729
852
|
}
|
|
853
|
+
for (const [i, st] of (report.steps ?? []).entries()) {
|
|
854
|
+
const vars = Object.entries(st.vars ?? {});
|
|
855
|
+
const changed = vars.length ? vars.slice(0, 6).map(([k, [a, b]]) => `${k} ${fmtVar(a)}→${fmtVar(b)}`).join(', ') + (vars.length > 6 ? `, … (${vars.length - 6} more)` : '') : 'no variable changed';
|
|
856
|
+
const over = st.hits ? ` — over ${st.hits.length ? st.hits.join(', ') : 'nothing'}` : '';
|
|
857
|
+
L.push(` do ${i + 1} ${st.label}${over} · ${st.error ? 'NOT RUN: ' + st.error : changed}${st.warnings?.length ? ` · ${st.warnings.length} warning(s)` : ''}${st.picture ? '' : ''}`);
|
|
858
|
+
}
|
|
730
859
|
if (report.stops) {
|
|
731
860
|
const s = report.stops;
|
|
732
861
|
L.push(` stops ${s.count} stop(s) on ${s.pages} page(s) — stops.png shows each one in order; the picture at a stop is what the audience sees while it waits`)
|
package/dist/Controller.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { M as Movie } from './Movie-
|
|
1
|
+
import { M as Movie } from './Movie-B3VOAgwc.cjs';
|
|
2
2
|
import 'pixi.js';
|
|
3
3
|
import './soundEntry-DT13DNt8.cjs';
|
|
4
|
-
import './types-
|
|
4
|
+
import './types-C5xo0fzb.cjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* The look of the bar. Each key sets a CSS custom property on the bar (so you can also set `--mc-accent` … in your own CSS, on the
|
package/dist/Controller.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { M as Movie } from './Movie-
|
|
1
|
+
import { M as Movie } from './Movie-CQCVUqRs.js';
|
|
2
2
|
import 'pixi.js';
|
|
3
3
|
import './soundEntry-DT13DNt8.js';
|
|
4
|
-
import './types-
|
|
4
|
+
import './types-C5xo0fzb.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* The look of the bar. Each key sets a CSS custom property on the bar (so you can also set `--mc-accent` … in your own CSS, on the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Application } from 'pixi.js';
|
|
2
2
|
import { A as AudioReport, a as AudioInspectOptions } from './soundEntry-DT13DNt8.cjs';
|
|
3
|
-
import {
|
|
3
|
+
import { a as Stop, A as AssetSpec, C as CompositionSpec } from './types-C5xo0fzb.cjs';
|
|
4
4
|
|
|
5
5
|
type EaseFn = (p: number) => number;
|
|
6
6
|
|
|
@@ -213,6 +213,8 @@ interface LayerInfo {
|
|
|
213
213
|
/** A `three` layer: the renderer it draws with (`'webgl'` / `'webgpu'`) and the backend under it (`'webgl2'`: three's WebGPURenderer found no WebGPU and used its own WebGL2 backend: the same picture). */
|
|
214
214
|
renderer?: 'webgl' | 'webgpu';
|
|
215
215
|
backend?: 'webgl' | 'webgpu' | 'webgl2';
|
|
216
|
+
/** A `three` layer with `renderer: 'webgpu'`: true when it draws on PixiJS's own GPU device (no canvas of its own); false when it has a canvas of its own (`shareGpu: false`, or PixiJS is on WebGL). */
|
|
217
|
+
sharedGpu?: boolean;
|
|
216
218
|
}
|
|
217
219
|
interface InspectReport {
|
|
218
220
|
frame: number;
|
|
@@ -273,8 +275,8 @@ interface PickHit {
|
|
|
273
275
|
}
|
|
274
276
|
interface PickOptions {
|
|
275
277
|
/**
|
|
276
|
-
* Look at the picture, not the box: a pixel of an image, a video
|
|
277
|
-
* Off by default (the box is the picture, and nothing is read). Shapes are always hit by their outline,
|
|
278
|
+
* Look at the picture, not the box: a pixel of an image, a video, a `canvas` layer or the letters of a text layer whose alpha is at or below this (0..1; `0.1` leaves out what is nearly clear) is not a hit.
|
|
279
|
+
* Off by default (the box is the picture, and nothing is read). Shapes are always hit by their outline, a `shader` layer by its box (and text by its box too unless `alpha` is given); a picture that cannot be read (from another origin) stays a hit.
|
|
278
280
|
*/
|
|
279
281
|
alpha?: number;
|
|
280
282
|
/** Only these layers can be hit (by `name` or by `path`; a group's name stands for everything in it). */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Application } from 'pixi.js';
|
|
2
2
|
import { A as AudioReport, a as AudioInspectOptions } from './soundEntry-DT13DNt8.js';
|
|
3
|
-
import {
|
|
3
|
+
import { a as Stop, A as AssetSpec, C as CompositionSpec } from './types-C5xo0fzb.js';
|
|
4
4
|
|
|
5
5
|
type EaseFn = (p: number) => number;
|
|
6
6
|
|
|
@@ -213,6 +213,8 @@ interface LayerInfo {
|
|
|
213
213
|
/** A `three` layer: the renderer it draws with (`'webgl'` / `'webgpu'`) and the backend under it (`'webgl2'`: three's WebGPURenderer found no WebGPU and used its own WebGL2 backend: the same picture). */
|
|
214
214
|
renderer?: 'webgl' | 'webgpu';
|
|
215
215
|
backend?: 'webgl' | 'webgpu' | 'webgl2';
|
|
216
|
+
/** A `three` layer with `renderer: 'webgpu'`: true when it draws on PixiJS's own GPU device (no canvas of its own); false when it has a canvas of its own (`shareGpu: false`, or PixiJS is on WebGL). */
|
|
217
|
+
sharedGpu?: boolean;
|
|
216
218
|
}
|
|
217
219
|
interface InspectReport {
|
|
218
220
|
frame: number;
|
|
@@ -273,8 +275,8 @@ interface PickHit {
|
|
|
273
275
|
}
|
|
274
276
|
interface PickOptions {
|
|
275
277
|
/**
|
|
276
|
-
* Look at the picture, not the box: a pixel of an image, a video
|
|
277
|
-
* Off by default (the box is the picture, and nothing is read). Shapes are always hit by their outline,
|
|
278
|
+
* Look at the picture, not the box: a pixel of an image, a video, a `canvas` layer or the letters of a text layer whose alpha is at or below this (0..1; `0.1` leaves out what is nearly clear) is not a hit.
|
|
279
|
+
* Off by default (the box is the picture, and nothing is read). Shapes are always hit by their outline, a `shader` layer by its box (and text by its box too unless `alpha` is given); a picture that cannot be read (from another origin) stays a hit.
|
|
278
280
|
*/
|
|
279
281
|
alpha?: number;
|
|
280
282
|
/** Only these layers can be hit (by `name` or by `path`; a group's name stands for everything in it). */
|