pixi-effects 0.43.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +5 -5
  3. package/ai/chat-template.html +3 -3
  4. package/ai/tools/check.mjs +131 -2
  5. package/dist/Controller.d.cts +2 -2
  6. package/dist/Controller.d.ts +2 -2
  7. package/dist/{Movie-BdUm2Mc9.d.cts → Movie-B3VOAgwc.d.cts} +3 -3
  8. package/dist/{Movie-B_qQIYV6.d.ts → Movie-CQCVUqRs.d.ts} +3 -3
  9. package/dist/{chunk-6N5ZBUOU.js → chunk-4MBTYREC.js} +81 -31
  10. package/dist/chunk-4MBTYREC.js.map +1 -0
  11. package/dist/{chunk-BEGOKE7Q.cjs → chunk-MAAM45ZX.cjs} +80 -30
  12. package/dist/chunk-MAAM45ZX.cjs.map +1 -0
  13. package/dist/index.cjs +25 -25
  14. package/dist/index.d.cts +3 -4
  15. package/dist/index.d.ts +3 -4
  16. package/dist/index.js +1 -1
  17. package/dist/loader.css +1 -1
  18. package/dist/present.cjs +1 -1
  19. package/dist/present.d.cts +2 -2
  20. package/dist/present.d.ts +2 -2
  21. package/dist/present.js +1 -1
  22. package/dist/three.cjs +86 -16
  23. package/dist/three.cjs.map +1 -1
  24. package/dist/three.d.cts +22 -2
  25. package/dist/three.d.ts +22 -2
  26. package/dist/three.js +86 -16
  27. package/dist/three.js.map +1 -1
  28. package/dist/{types-DiNHtdrS.d.cts → types-C5xo0fzb.d.cts} +74 -2
  29. package/dist/{types-DiNHtdrS.d.ts → types-C5xo0fzb.d.ts} +74 -2
  30. package/docs/api.md +4 -2
  31. package/docs/dsl.md +1 -0
  32. package/llms-full.txt +9 -6
  33. package/package.json +1 -1
  34. package/skills/pixi-effects/SKILL.md +2 -2
  35. package/skills/pixi-effects/reference/recipes.md +2 -2
  36. package/skills/pixi-effects/template.html +6 -6
  37. package/dist/Base-DKFQtqzW.d.ts +0 -60
  38. package/dist/Base-lb5Uhaqu.d.cts +0 -60
  39. package/dist/chunk-6N5ZBUOU.js.map +0 -1
  40. package/dist/chunk-BEGOKE7Q.cjs.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
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
+
3
13
  ## 0.43.0
4
14
 
5
15
  A `three` layer on WebGPU shares PixiJS's GPU device, and the tests stop depending on the gallery. Nothing breaks.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # pixi-effects
2
2
 
3
- > **Status**: experimental — current release `0.43.0`. The API may still change between minor versions; see the [CHANGELOG](./CHANGELOG.md) for what each release changed.
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.43.0/dist/index.js",
53
- "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.43.0/dist/Controller.js"
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.43.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.
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.43.0/dist/three.js"
81
+ "pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.44.0/dist/three.js"
82
82
  }
83
83
  }
84
84
  </script>
@@ -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.43.0/dist/index.js",
63
- "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.43.0/dist/Controller.js",
64
- "pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.43.0/dist/present.js"
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>
@@ -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`)
@@ -1,7 +1,7 @@
1
- import { M as Movie } from './Movie-BdUm2Mc9.cjs';
1
+ import { M as Movie } from './Movie-B3VOAgwc.cjs';
2
2
  import 'pixi.js';
3
3
  import './soundEntry-DT13DNt8.cjs';
4
- import './types-DiNHtdrS.cjs';
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
@@ -1,7 +1,7 @@
1
- import { M as Movie } from './Movie-B_qQIYV6.js';
1
+ import { M as Movie } from './Movie-CQCVUqRs.js';
2
2
  import 'pixi.js';
3
3
  import './soundEntry-DT13DNt8.js';
4
- import './types-DiNHtdrS.js';
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 { b as Stop, A as AssetSpec, a as CompositionSpec } from './types-DiNHtdrS.cjs';
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
 
@@ -275,8 +275,8 @@ interface PickHit {
275
275
  }
276
276
  interface PickOptions {
277
277
  /**
278
- * Look at the picture, not the box: a pixel of an image, a video or a `canvas` 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, text and `shader` layers by their box; a picture that cannot be read (from another origin) stays a hit.
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.
280
280
  */
281
281
  alpha?: number;
282
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 { b as Stop, A as AssetSpec, a as CompositionSpec } from './types-DiNHtdrS.js';
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
 
@@ -275,8 +275,8 @@ interface PickHit {
275
275
  }
276
276
  interface PickOptions {
277
277
  /**
278
- * Look at the picture, not the box: a pixel of an image, a video or a `canvas` 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, text and `shader` layers by their box; a picture that cannot be read (from another origin) stays a hit.
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.
280
280
  */
281
281
  alpha?: number;
282
282
  /** Only these layers can be hit (by `name` or by `path`; a group's name stands for everything in it). */
@@ -4,7 +4,7 @@ import { analyzeAudio, fft, resampleThrough, playSpan } from './chunk-UHNSL2KJ.j
4
4
  import { usesAdvancedBlend, enableAdvancedBlend } from './chunk-VNP7QCJE.js';
5
5
  import { describeLayer } from './chunk-32MMNDDN.js';
6
6
  import { warnUnknownOptions, suggestName } from './chunk-BXOCEZ4U.js';
7
- import { extensions, CullerPlugin, Application, Container, Rectangle, Culler, CanvasTextMetrics, TextStyle, Assets, ExtensionType, Filter, Texture, UniformGroup, GpuProgram, GlProgram, defaultFilterVert } from 'pixi.js';
7
+ import { extensions, CullerPlugin, Application, Container, Rectangle, Culler, CanvasTextMetrics, TextStyle, Assets, ExtensionType, Filter, Texture, UniformGroup, GpuProgram, GlProgram, defaultFilterVert, Text, CanvasTextGenerator, CanvasPool } from 'pixi.js';
8
8
  import { Output, BufferTarget, CanvasSource, AudioBufferSource, Input, ALL_FORMATS, BlobSource, WavOutputFormat, Conversion, VideoSampleSink, OggOutputFormat, QUALITY_VERY_HIGH, QUALITY_HIGH, QUALITY_MEDIUM, QUALITY_LOW, QUALITY_VERY_LOW, MkvOutputFormat, WebMOutputFormat, MovOutputFormat, Mp4OutputFormat, canEncodeVideo, getFirstEncodableAudioCodec } from 'mediabunny';
9
9
 
10
10
  // src/core/gpuShare.ts
@@ -188,7 +188,7 @@ function inspectFonts(root) {
188
188
  });
189
189
  const missing = [];
190
190
  const used = /* @__PURE__ */ new Set();
191
- const walk2 = (seq, index) => {
191
+ const walk3 = (seq, index) => {
192
192
  const spec = seq.spec;
193
193
  if (spec.type === "text") {
194
194
  const raw = spec.style?.fontFamily;
@@ -197,10 +197,10 @@ function inspectFonts(root) {
197
197
  for (const f of list) used.add(f.replace(/^["']|["']$/g, "").toLowerCase());
198
198
  if (list.length && !list.some(fontAvailable) && !list.some((f) => failed.has(f.replace(/^["']|["']$/g, "")))) missing.push({ layer: spec.name ?? `text#${index}`, family: family2 });
199
199
  }
200
- if (seq.maskSequence) walk2(seq.maskSequence, index);
201
- (seq._children ?? []).forEach((child, i) => walk2(child, i));
200
+ if (seq.maskSequence) walk3(seq.maskSequence, index);
201
+ (seq._children ?? []).forEach((child, i) => walk3(child, i));
202
202
  };
203
- walk2(root, 0);
203
+ walk3(root, 0);
204
204
  const all = [...failed];
205
205
  return { missing, failed: all.filter((f) => used.has(f.toLowerCase())), failedUnused: all.filter((f) => !used.has(f.toLowerCase())) };
206
206
  }
@@ -1526,16 +1526,16 @@ function canvasReport(root) {
1526
1526
  const problem = threw.length ? `${name} could not draw at ${threw.map((t) => `${t} s`).join(", ")} (its ${kind === "three" ? "update or render threw" : "draw threw"}, or the layer failed to build): ${kind === "three" ? "the warning above says why" : "it shows a magenta checkerboard there; the warning above says why"}, so the layer cannot be called deterministic` : differs.length ? `${name} does not draw the same picture for the same time (checked at ${differs.map(describeDiff).join(", ")}; drawn in two orders after one draw that was thrown away): ` + (kind === "three" ? "usually its update uses Math.random, Date.now, a clock, or state that builds up from call to call: a mixer advanced with mixer.update(dt) is one (pose a clip with mixer.setTime(t), or threeGltf(url).pose(t)), and so is a value moved by the time since the last call. A skinned model of your own (not loaded by threeGltf) that moves and casts a shadow does it too: three culls a skinned mesh by a bounding sphere that does not follow it, so set mesh.frustumCulled = false. Set every object from t, so playback, a seek and an export agree" : "usually its draw uses Math.random, Date.now or state that outlives the call (a value it keeps from the last call), so playback, a seek and an export would not agree: use info.random() (seeded) and derive everything from t. If it uses none of these, look at the canvases it draws from: make sprites and masks with info.canvas(w, h) (not document.createElement) and draw them into the picture before the strokes that depend on them") : "";
1527
1527
  out.push({ name: seq.spec.name ?? name, moments, drawMs, ok: !differs.length && !threw.length, problem, kind });
1528
1528
  }
1529
- if (seq instanceof CompositionSequence) walk2(seq);
1529
+ if (seq instanceof CompositionSequence) walk3(seq);
1530
1530
  };
1531
- const walk2 = (comp) => {
1531
+ const walk3 = (comp) => {
1532
1532
  for (const { seq } of comp.layers()) {
1533
1533
  visit(seq);
1534
1534
  const mask = seq.maskSequence;
1535
1535
  if (mask) visit(mask);
1536
1536
  }
1537
1537
  };
1538
- walk2(root);
1538
+ walk3(root);
1539
1539
  return out;
1540
1540
  }
1541
1541
  var canvasProblems = (root) => canvasReport(root).filter((r) => !r.ok).map((r) => r.problem);
@@ -1602,7 +1602,7 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1602
1602
  const specs = /* @__PURE__ */ new Map();
1603
1603
  const chains = /* @__PURE__ */ new Map();
1604
1604
  const chainAlpha = /* @__PURE__ */ new Map();
1605
- const walk2 = (comp, prefix, insideThreeD, parentVisible, parentScene, parentMotion = NO_MOTION, parentZero = false, parentAlpha = 1) => {
1605
+ const walk3 = (comp, prefix, insideThreeD, parentVisible, parentScene, parentMotion = NO_MOTION, parentZero = false, parentAlpha = 1) => {
1606
1606
  const blending = /* @__PURE__ */ new Set();
1607
1607
  const now = comp.childTime(time);
1608
1608
  const cameraMotion = (comp.spec.sequences ?? []).filter((q) => q.type === "camera").reduce((m, q) => either(m, motionOf(q.keyframes, now - comp.childBase - (q.at ?? 0), q.duration ?? comp.duration ?? Infinity, () => true)), NO_MOTION);
@@ -1668,10 +1668,10 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1668
1668
  const group = parentScene ?? (name !== void 0 && blending.has(name) ? name : void 0);
1669
1669
  if (group) scene.set(info, group);
1670
1670
  if (zero || seq.showsNothingYet) scaledToNothing.add(info);
1671
- if (seq instanceof CompositionSequence) walk2(seq, prefix + label + "/", insideThreeD || threeD, visible, group, motion, zero, parentAlpha * alpha);
1671
+ if (seq instanceof CompositionSequence) walk3(seq, prefix + label + "/", insideThreeD || threeD, visible, group, motion, zero, parentAlpha * alpha);
1672
1672
  });
1673
1673
  };
1674
- walk2(root, "", false, true);
1674
+ walk3(root, "", false, true);
1675
1675
  const issues = [];
1676
1676
  const texts = layers.filter((l) => l.type === "text" && l.visible && l.alpha >= FAINT && l.bounds);
1677
1677
  for (const l of texts) {
@@ -1798,7 +1798,7 @@ function pickScene(root, x, y, opts = {}, deps = {}) {
1798
1798
  return true;
1799
1799
  };
1800
1800
  const inside = (b, p) => p.x >= b.x && p.x <= b.x + b.width && p.y >= b.y && p.y <= b.y + b.height;
1801
- const walk2 = (comp, prefix, parentVisible) => {
1801
+ const walk3 = (comp, prefix, parentVisible) => {
1802
1802
  comp.layers().forEach(({ seq, display, threeD, carriers, quad }, i) => {
1803
1803
  const d = display;
1804
1804
  if (!d) return;
@@ -1811,7 +1811,7 @@ function pickScene(root, x, y, opts = {}, deps = {}) {
1811
1811
  if (!visible) return;
1812
1812
  const isComposition = seq instanceof CompositionSequence;
1813
1813
  if (isComposition && !threeD) {
1814
- walk2(seq, path + "/", visible);
1814
+ walk3(seq, path + "/", visible);
1815
1815
  return;
1816
1816
  }
1817
1817
  if (!wanted(name, path)) return;
@@ -1839,7 +1839,7 @@ function pickScene(root, x, y, opts = {}, deps = {}) {
1839
1839
  hit = { name, path, type: seq.spec.type, kind: "three", point, three };
1840
1840
  } else if (three === void 0 && (typeof d.containsPoint !== "function" || d.containsPoint(local))) {
1841
1841
  let opaque = true;
1842
- const tex = opts.alpha !== void 0 && seq.spec.type !== "shader" ? d.texture : void 0;
1842
+ const tex = opts.alpha !== void 0 && seq.spec.type !== "shader" ? d.texture ?? deps.textureOf?.(d) : void 0;
1843
1843
  if (tex?.source?.resource) {
1844
1844
  const res = tex.source.resolution ?? 1;
1845
1845
  const a = readAlpha(tex.source.resource, (tex.frame.x + (local.x - lb.x) / lb.width * tex.frame.width) * res, (tex.frame.y + (local.y - lb.y) / lb.height * tex.frame.height) * res);
@@ -1861,9 +1861,56 @@ function pickScene(root, x, y, opts = {}, deps = {}) {
1861
1861
  found.push({ hit, order: orderOf(d) });
1862
1862
  });
1863
1863
  };
1864
- walk2(root, "", true);
1864
+ walk3(root, "", true);
1865
1865
  return found.sort((a, b) => above(a.order, b.order)).map((f) => f.hit);
1866
1866
  }
1867
+ var cache = /* @__PURE__ */ new WeakMap();
1868
+ function textPictureOf(display) {
1869
+ if (!(display instanceof Text)) return null;
1870
+ const style = display.style;
1871
+ const key = `${display.text}|${display.resolution}|${style.styleKey ?? ""}`;
1872
+ const kept = cache.get(display);
1873
+ if (kept?.key === key) return kept.picture;
1874
+ try {
1875
+ const { canvasAndContext, frame } = CanvasTextGenerator.getCanvasAndContext({ text: display.text, style: display.style, resolution: display.resolution });
1876
+ const copy = document.createElement("canvas");
1877
+ copy.width = canvasAndContext.canvas.width;
1878
+ copy.height = canvasAndContext.canvas.height;
1879
+ copy.getContext("2d").drawImage(canvasAndContext.canvas, 0, 0);
1880
+ CanvasPool.returnCanvasAndContext(canvasAndContext);
1881
+ const picture = { frame: { x: frame.x, y: frame.y, width: frame.width, height: frame.height }, source: { resolution: display.resolution, resource: copy } };
1882
+ cache.set(display, { key, picture });
1883
+ return picture;
1884
+ } catch {
1885
+ return null;
1886
+ }
1887
+ }
1888
+
1889
+ // src/core/reuse.ts
1890
+ var lendable = (s) => typeof s.reuseKey === "function";
1891
+ var borrower = (s) => typeof s.commitReuse === "function";
1892
+ function walk(root, visit) {
1893
+ for (const { seq } of root.layers()) {
1894
+ visit(seq);
1895
+ const mask = seq.maskSequence;
1896
+ if (mask) visit(mask);
1897
+ if (seq instanceof CompositionSequence) walk(seq, visit);
1898
+ }
1899
+ }
1900
+ function reusePoolOf(old) {
1901
+ const free = /* @__PURE__ */ new Map();
1902
+ if (old) walk(old, (seq) => {
1903
+ if (!lendable(seq)) return;
1904
+ const key = seq.reuseKey();
1905
+ if (key !== null) free.set(key, [...free.get(key) ?? [], seq]);
1906
+ });
1907
+ return { claim: (key) => free.get(key)?.shift() ?? null };
1908
+ }
1909
+ function commitReuse(root) {
1910
+ walk(root, (seq) => {
1911
+ if (borrower(seq)) seq.commitReuse();
1912
+ });
1913
+ }
1867
1914
 
1868
1915
  // src/core/hover.ts
1869
1916
  var keyOf = (h) => h ? `${h.path}${h.three ? `#${h.three.object}` : ""}` : null;
@@ -1957,7 +2004,7 @@ function detailOf(seq) {
1957
2004
  if (s.music && typeof s.music === "object") return "music";
1958
2005
  return void 0;
1959
2006
  }
1960
- function walk(comp, parentStart, parentEnd, depth, prefix, rows, transitions, local) {
2007
+ function walk2(comp, parentStart, parentEnd, depth, prefix, rows, transitions, local) {
1961
2008
  const entries = [];
1962
2009
  comp._children.forEach((seq, i) => {
1963
2010
  const name = seq.spec.name ?? `${seq.spec.type}#${i}`;
@@ -1970,7 +2017,7 @@ function walk(comp, parentStart, parentEnd, depth, prefix, rows, transitions, lo
1970
2017
  const isComp = seq instanceof CompositionSequence;
1971
2018
  if (isComp) {
1972
2019
  const label = seq.remapLabel();
1973
- walk(seq, label ? 0 : start, label ? seq.contentSpan : end, depth + 1, prefix + name + "/", inner, transitions, label ? `${name} ${label}` : local);
2020
+ walk2(seq, label ? 0 : start, label ? seq.contentSpan : end, depth + 1, prefix + name + "/", inner, transitions, label ? `${name} ${label}` : local);
1974
2021
  }
1975
2022
  entries.push({ row, inner, groupable: !isComp });
1976
2023
  });
@@ -2018,7 +2065,7 @@ function collectTimeline(root, duration) {
2018
2065
  const rows = [];
2019
2066
  const transitions = [];
2020
2067
  const label = root.remapLabel();
2021
- walk(root, 0, label ? root.contentSpan : duration, 0, "", rows, transitions, label ? `movie ${label}` : void 0);
2068
+ walk2(root, 0, label ? root.contentSpan : duration, 0, "", rows, transitions, label ? `movie ${label}` : void 0);
2022
2069
  return { duration, rows, transitions };
2023
2070
  }
2024
2071
  var COLORS = {
@@ -2166,28 +2213,28 @@ function substitute(s, vars) {
2166
2213
  }
2167
2214
  function applyVars(spec, vars) {
2168
2215
  if (Object.keys(vars).length === 0) return spec;
2169
- const walk2 = (v) => {
2216
+ const walk3 = (v) => {
2170
2217
  if (typeof v === "string") return substitute(v, vars);
2171
- if (Array.isArray(v)) return v.map(walk2);
2218
+ if (Array.isArray(v)) return v.map(walk3);
2172
2219
  if (isPlain(v)) {
2173
2220
  const o = {};
2174
- for (const [k, x] of Object.entries(v)) o[k] = walk2(x);
2221
+ for (const [k, x] of Object.entries(v)) o[k] = walk3(x);
2175
2222
  return o;
2176
2223
  }
2177
2224
  return v;
2178
2225
  };
2179
- return walk2(spec);
2226
+ return walk3(spec);
2180
2227
  }
2181
2228
  var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2182
2229
  function lintVars(spec, vars) {
2183
2230
  const declared = Object.keys(vars);
2184
2231
  const strings = [];
2185
- const walk2 = (v) => {
2232
+ const walk3 = (v) => {
2186
2233
  if (typeof v === "string") strings.push(v);
2187
- else if (Array.isArray(v)) v.forEach(walk2);
2188
- else if (isPlain(v)) Object.values(v).forEach(walk2);
2234
+ else if (Array.isArray(v)) v.forEach(walk3);
2235
+ else if (isPlain(v)) Object.values(v).forEach(walk3);
2189
2236
  };
2190
- walk2(spec);
2237
+ walk3(spec);
2191
2238
  const out = [];
2192
2239
  for (const name of declared) {
2193
2240
  const word = new RegExp(`(?<![A-Za-z0-9_.])${escapeRe(name)}(?![A-Za-z0-9_])`);
@@ -2741,7 +2788,7 @@ var Movie = class {
2741
2788
  * Build the composition tree for these variables and this size, off stage: `{name}` replaced, the root shape (with the numeric variables for the
2742
2789
  * expressions), transitions expanded, every layer built. Used by `init` and by `rebuild`.
2743
2790
  */
2744
- async _construct(composition, vars, width, height, lp, sizeWins = this._resized) {
2791
+ async _construct(composition, vars, width, height, lp, sizeWins = this._resized, reuse) {
2745
2792
  const spec = applyVars(composition, vars);
2746
2793
  const gpu = sharedGpuOf(this.app?.renderer);
2747
2794
  const gpuField = gpu ? { gpu } : {};
@@ -2752,6 +2799,7 @@ var Movie = class {
2752
2799
  frameRate: this.frameRate,
2753
2800
  vars: numericVars(vars),
2754
2801
  ...gpuField,
2802
+ ...reuse ? { reuse } : {},
2755
2803
  ...lp ? { onLayerBuilt: async () => {
2756
2804
  lp.tick();
2757
2805
  await lp.yieldIfDue();
@@ -2908,7 +2956,7 @@ var Movie = class {
2908
2956
  if (change.composition !== void 0) for (const w of lintVars(change.composition, vars)) console.warn(w);
2909
2957
  changed = changed || sizeChanged || change.composition !== void 0 || reason === "rebuild";
2910
2958
  if (!changed) return { reason, ms: 0, audio: "none", frame: this.currentFrame, playing: this.isPlaying, unchanged: true };
2911
- sequence = await this._construct(composition, vars, width, height, void 0, this._resized || sizeChanged);
2959
+ sequence = await this._construct(composition, vars, width, height, void 0, this._resized || sizeChanged, reusePoolOf(this._rootSequence));
2912
2960
  alive();
2913
2961
  const timeline = createTimeline({ paused: true, defaults: { ease: "none" } });
2914
2962
  lengthen(timeline, this.duration);
@@ -2948,6 +2996,7 @@ var Movie = class {
2948
2996
  if (old?.target) root.removeChild(old.target);
2949
2997
  if (sequence.target) root.addChild(sequence.target);
2950
2998
  this._rootSequence = sequence;
2999
+ commitReuse(sequence);
2951
3000
  this.timeline = timeline;
2952
3001
  this._timeFilters = collectTimeFilters(sequence);
2953
3002
  const unmute = muteBoundTextureWarning();
@@ -3222,7 +3271,8 @@ var Movie = class {
3222
3271
  warnUnknownOptions("movie.pick()", opts, ["only", "ignore", "alpha"]);
3223
3272
  this._requireReady("pick");
3224
3273
  if (!Number.isFinite(x) || !Number.isFinite(y)) throw new Error(`pixi-effects: movie.pick(x, y) needs two numbers (canvas pixels), got ${x}, ${y}. For a pointer event: movie.pick(...movie.canvasPoint(event))`);
3225
- return pickScene(this._rootSequence, x, y, opts);
3274
+ const textureOf = textPictureOf;
3275
+ return pickScene(this._rootSequence, x, y, opts, { textureOf });
3226
3276
  }
3227
3277
  /**
3228
3278
  * Which layer the pointer is over, as callbacks: `onEnter(hit)` when the layer (or, in a `three` layer, the object) under the pointer changes to another, `onLeave(hit)` for the one it
@@ -4743,5 +4793,5 @@ function withFade(spec, opts) {
4743
4793
  }
4744
4794
 
4745
4795
  export { Movie, animateText, audioEnvelope, bpmEnvelope, cameraPath, computeEnvelope, dismissLoader, failLoader, followPath, kenBurns, measureText, musicBuffer, musicEnvelope, musicEvents, orbit, parseEase2 as parseEase, particles, react, resolveLoader, samplePath, splitText, stagger, wiggle, withFade };
4746
- //# sourceMappingURL=chunk-6N5ZBUOU.js.map
4747
- //# sourceMappingURL=chunk-6N5ZBUOU.js.map
4796
+ //# sourceMappingURL=chunk-4MBTYREC.js.map
4797
+ //# sourceMappingURL=chunk-4MBTYREC.js.map