pixi-effects 0.48.2 → 0.49.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 (88) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +51 -7
  3. package/ai/chat-template.html +8 -4
  4. package/ai/tools/check.mjs +6 -6
  5. package/dist/3d.cjs +963 -0
  6. package/dist/3d.cjs.map +1 -0
  7. package/dist/3d.d.cts +303 -0
  8. package/dist/3d.d.ts +303 -0
  9. package/dist/3d.js +956 -0
  10. package/dist/3d.js.map +1 -0
  11. package/dist/Canvas-BfZkAFJm.d.cts +12 -0
  12. package/dist/Canvas-BfZkAFJm.d.ts +12 -0
  13. package/dist/Composition-464AWDV6.cjs +16 -0
  14. package/dist/{Composition-TJ5D7Z63.cjs.map → Composition-464AWDV6.cjs.map} +1 -1
  15. package/dist/Composition-5U63LMPX.js +7 -0
  16. package/dist/{Composition-5FKV2IBP.js.map → Composition-5U63LMPX.js.map} +1 -1
  17. package/dist/Controller.d.cts +2 -2
  18. package/dist/Controller.d.ts +2 -2
  19. package/dist/{Movie--qSYKgAQ.d.ts → Movie-6EWOz6Yw.d.ts} +21 -5
  20. package/dist/{Movie-svZQF-9Z.d.cts → Movie-BxB7bauG.d.cts} +21 -5
  21. package/dist/{Spans-5ENZ3XGS.cjs → Spans-DUR2W5EH.cjs} +11 -11
  22. package/dist/{Spans-5ENZ3XGS.cjs.map → Spans-DUR2W5EH.cjs.map} +1 -1
  23. package/dist/{Spans-6A3VLHRH.js → Spans-W5VJRMGC.js} +6 -6
  24. package/dist/{Spans-6A3VLHRH.js.map → Spans-W5VJRMGC.js.map} +1 -1
  25. package/dist/{blendModes-V5XHC7U4.cjs → blendModes-NC76DNVX.cjs} +5 -5
  26. package/dist/{blendModes-V5XHC7U4.cjs.map → blendModes-NC76DNVX.cjs.map} +1 -1
  27. package/dist/{blendModes-535EMYGK.js → blendModes-ZOBLPSR3.js} +4 -4
  28. package/dist/{blendModes-535EMYGK.js.map → blendModes-ZOBLPSR3.js.map} +1 -1
  29. package/dist/{chunk-Z3DM4ETD.cjs → chunk-33USHRV5.cjs} +43 -32
  30. package/dist/chunk-33USHRV5.cjs.map +1 -0
  31. package/dist/{chunk-WYR2AU4W.cjs → chunk-5YNME6AH.cjs} +5 -5
  32. package/dist/{chunk-WYR2AU4W.cjs.map → chunk-5YNME6AH.cjs.map} +1 -1
  33. package/dist/{chunk-NGJXVJB7.cjs → chunk-ABWRISRK.cjs} +3 -2
  34. package/dist/chunk-ABWRISRK.cjs.map +1 -0
  35. package/dist/{chunk-CCPJD76D.cjs → chunk-BSFWVTSR.cjs} +93 -93
  36. package/dist/chunk-BSFWVTSR.cjs.map +1 -0
  37. package/dist/{chunk-IVKI6CPH.js → chunk-DOGY3EAW.js} +19 -9
  38. package/dist/chunk-DOGY3EAW.js.map +1 -0
  39. package/dist/{chunk-45ALIMAF.cjs → chunk-GUDLIAIG.cjs} +58 -55
  40. package/dist/chunk-GUDLIAIG.cjs.map +1 -0
  41. package/dist/{chunk-BFRWSV3R.js → chunk-H363KMJR.js} +3 -3
  42. package/dist/chunk-H363KMJR.js.map +1 -0
  43. package/dist/{chunk-JBDBICS2.js → chunk-MXACRKAN.js} +4 -4
  44. package/dist/{chunk-JBDBICS2.js.map → chunk-MXACRKAN.js.map} +1 -1
  45. package/dist/{chunk-RCKLUCB7.js → chunk-PBMDZT2T.js} +15 -12
  46. package/dist/chunk-PBMDZT2T.js.map +1 -0
  47. package/dist/{chunk-6D4W2KZQ.js → chunk-USUUAYTE.js} +6 -6
  48. package/dist/chunk-USUUAYTE.js.map +1 -0
  49. package/dist/index.cjs +35 -35
  50. package/dist/index.d.cts +3 -3
  51. package/dist/index.d.ts +3 -3
  52. package/dist/index.js +5 -5
  53. package/dist/loader.css +1 -1
  54. package/dist/{music-P4JRT73P.js → music-ATX22VAT.js} +3 -3
  55. package/dist/{music-P4JRT73P.js.map → music-ATX22VAT.js.map} +1 -1
  56. package/dist/{music-XQEPBIDT.cjs → music-OFVM3PYH.cjs} +3 -3
  57. package/dist/{music-XQEPBIDT.cjs.map → music-OFVM3PYH.cjs.map} +1 -1
  58. package/dist/present.cjs +5 -5
  59. package/dist/present.d.cts +2 -2
  60. package/dist/present.d.ts +2 -2
  61. package/dist/present.js +5 -5
  62. package/dist/three.cjs +7 -8
  63. package/dist/three.cjs.map +1 -1
  64. package/dist/three.d.cts +2 -12
  65. package/dist/three.d.ts +2 -12
  66. package/dist/three.js +2 -3
  67. package/dist/three.js.map +1 -1
  68. package/dist/{types-CTSrPvY3.d.cts → types-kt7DQKfO.d.cts} +1 -1
  69. package/dist/{types-CTSrPvY3.d.ts → types-kt7DQKfO.d.ts} +1 -1
  70. package/docs/api.md +41 -1
  71. package/docs/dsl.md +81 -1
  72. package/llms-full.txt +218 -5
  73. package/package.json +19 -3
  74. package/skills/pixi-effects/SKILL.md +3 -2
  75. package/skills/pixi-effects/reference/cheatsheet.md +2 -0
  76. package/skills/pixi-effects/reference/pitfalls.md +14 -0
  77. package/skills/pixi-effects/reference/recipes.md +77 -1
  78. package/skills/pixi-effects/template.html +13 -8
  79. package/dist/Composition-5FKV2IBP.js +0 -7
  80. package/dist/Composition-TJ5D7Z63.cjs +0 -16
  81. package/dist/chunk-45ALIMAF.cjs.map +0 -1
  82. package/dist/chunk-6D4W2KZQ.js.map +0 -1
  83. package/dist/chunk-BFRWSV3R.js.map +0 -1
  84. package/dist/chunk-CCPJD76D.cjs.map +0 -1
  85. package/dist/chunk-IVKI6CPH.js.map +0 -1
  86. package/dist/chunk-NGJXVJB7.cjs.map +0 -1
  87. package/dist/chunk-RCKLUCB7.js.map +0 -1
  88. package/dist/chunk-Z3DM4ETD.cjs.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.49.0
4
+
5
+ Nothing breaks: 3D scenes drawn by PixiJS 3D (`@pixi/3d`), the new main 3D engine, as a `scene3d` layer (entry `pixi-effects/3d`); three.js stays as the sub engine. PixiJS 8.23 in the template.
6
+
7
+ - **3D scenes drawn by PixiJS 3D: the `scene3d` layer** (new optional entry `pixi-effects/3d`, `register3D()`, needs `@pixi/3d` and pixi.js 8.23+). PixiJS 3D (`@pixi/3d`, now public) becomes the main 3D engine; three.js stays as the sub engine under its own names. A scene3d layer is a free box like the three layer: `setup({ view, root, camera })` builds the scene with `@pixi/3d`, `update(t, ctx)` sets it from the time, keyframes reach `scene3d.<object>.<path>`. It is drawn inside PixiJS's own renderer, in the layer's place: no second renderer, no canvas, no copy, no context budget; masks, filters, nested compositions and `threeD` cards work on it, and the layer applies `alpha` (with its parents') and `add` / `screen` / `multiply`, which PixiJS 3D ignores on its own. A frame is a function of the time: models and animation players do not run on the ticker (a clip left playing is posed from the layer's time), the shader time `uTime` is the layer's time, `update` with a clock or `Math.random` warns, and `check` draws each scene3d layer in two orders and names one that differs. Typos (did you mean), keys only a three layer has, unknown keyframe paths, a failed `setup` and PixiJS 3D loaded after `movie.init` are each said in a sentence. Recipe `scene3d-basics`; template and README import maps carry `pixi-effects/3d`, `@pixi/3d`, `@pixi/3d/extras`. Measured: WebGL and WebGPU draw a lit scene within 0.03–0.04 of 255.
8
+ - **scene3d, the basic features complete** (phase 2): `followCamera` / `followLights` / `followFog` put a scene3d layer in the composition's own camera, lights and fog (the scene is in composition pixels, y flipped; planes land on the composition's cards within 0.02 of 255; lights in PixiJS 3D's linear light, so a 3D surface is brighter in the shade than a card; shadows from parallel and spot lights, which PixiJS 3D's defaults lost in a scene of pixels: the shadow box and the spot bias are set for it); `movie.pick` / hover / `hit.scene3d.grab(plane).move(x, y)` into the scene (`kind: 'scene3d'`, the object named by its label, picked per triangle); `layerTexture(ctx, 'screen')` shows a 2D layer on a mesh (a PixiJS texture drawn at every frame: identical to the flat layer; a pick on the mesh names the 2D layer); `loadModel(url, { animation, speed, offset, loop })` / `fitCamera(ctx, object)` for glTF models posed from the time; `reuse`: a named layer keeps its scene across `setVars` (`setup` runs once). A light layer with `castsShadows` no longer warns about missing casters when a scene3d layer follows the lights; an unregistered `scene3d` / `three` layer names its entry. Recipes `scene3d-basics` (now with `loadModel`) and `scene3d-in-space`.
9
+ - **PixiJS 8.23** in the template, the chat template and the library's own pages and tests (PixiJS 3D needs it; the main entry still accepts pixi.js 8.x).
10
+
3
11
  ## 0.48.2
4
12
 
5
13
  Nothing breaks: an app that bundles pixi-effects keeps about 100 KB (gzipped) less of pixi.js, and closing movies no longer uses up PixiJS's 500 warnings.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # pixi-effects
2
2
 
3
- > **Status**: experimental — current release `0.48.2`. The API may still change between minor versions; see the [CHANGELOG](./CHANGELOG.md) for what each release changed.
3
+ > **Status**: experimental — current release `0.49.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
 
@@ -25,16 +25,18 @@ Declarative composition and video rendering for the web. After Effects-style tim
25
25
  - **Music written as text** — `{ type: 'audio', music: { bpm: 96, tracks: [{ inst: 'keys', notes: 'Cmaj7:4 Am7:4' }], drums: { kick: 'x...x...' } } }`: a tune with no audio file, played by a small built-in synthesiser (keys, pluck, pad, bass, lead, bell, music box, drums; chords by name, swing, fades, a ritardando). Deterministic, in the exported file at the same moment; an AI writes it as easily as a title.
26
26
  - **Audio-reactive visuals** — `audioEnvelope('music.mp3')` analyses loudness, bass / mid / treble and the beats per frame; `react()` turns them into keyframes, so the picture follows the music and still renders identically.
27
27
  - **2.5D layers & camera** — add `threeD: true`, `z`, `rotationX/Y` and a `{ type: 'camera' }` layer for parallax, flips and dolly zooms; no three.js needed.
28
+ - **3D scenes** *(optional)* — a real 3D scene (meshes, glTF models, PBR, lights, shadows) as a layer via `pixi-effects/3d`, drawn by [PixiJS 3D](https://pixijs.com/3d) inside PixiJS's own renderer, so masks, filters, blend modes and opacity work on it like on any layer; keyframes drive its objects (`scene3d.cube.rotation.y`). It can live in the composition's space (`followCamera`, `followLights`, `followFog`), load and pose a glTF from the time (`loadModel`), show a 2D layer on a mesh (`layerTexture`) and be clicked into (`movie.pick`).
28
29
  - **three.js layer** *(optional)* — drop a real three.js scene in as a layer via `pixi-effects/three`; keyframes drive its objects (`three.cube.rotation.y`). It can live in the composition's space: the composition's camera, lights, shadows and fog drive the scene, so one `cameraPath()` flies through the flat layers and a glTF model together (`threeGltf`).
29
30
  - **Built-in player UI — or your own** — a drop-in HTML5-`<video>`-style overlay controller (play, scrub, mute, volume, fullscreen, export-to-file) whose colours are one option (`theme: { accent: '#ff4d6d' }`); or build a player yourself on the movie's `<video>`-named events (`play pause ended seeking seeked volumechange error`).
30
31
  - **MP4 / WebM / MOV export** — pick container and quality from the controller, call `movie.render()` from code, or render headless from the command line (`pixi-effects render`).
31
32
  - **A timeline you can read and scrub** — `movie.timelineChart()` draws every layer as a bar on a time axis; `pixi-effects view` opens the page with that timeline under it, zoomable, with a playhead you can drag.
32
- - **Tiny dependency surface** — only `mediabunny` (runtime) plus PixiJS (a peer dep). three.js is an *optional* peer, needed only if you import `pixi-effects/three`. mediabunny is fetched only when you export or use a video layer: a page that only plays never loads it.
33
+ - **Tiny dependency surface** — only `mediabunny` (runtime) plus PixiJS (a peer dep). `@pixi/3d` and three.js are *optional* peers, needed only if you import `pixi-effects/3d` or `pixi-effects/three`. mediabunny is fetched only when you export or use a video layer: a page that only plays never loads it.
33
34
 
34
35
  ## Install
35
36
 
36
37
  ```bash
37
38
  npm install pixi-effects pixi.js
39
+ npm install @pixi/3d # optional — only if you import `pixi-effects/3d` (needs pixi.js 8.23+)
38
40
  npm install three # optional — only if you import `pixi-effects/three`
39
41
  npm install pixi-filters # optional — only if a layer uses a named filter other than blur / noise / alpha / colorMatrix
40
42
  ```
@@ -49,8 +51,8 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
49
51
  "imports": {
50
52
  "pixi.js": "https://esm.sh/pixi.js@8.22.0?bundle-deps",
51
53
  "mediabunny": "https://esm.sh/mediabunny",
52
- "pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.48.2/dist/index.js",
53
- "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.48.2/dist/Controller.js"
54
+ "pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.49.0/dist/index.js",
55
+ "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.49.0/dist/Controller.js"
54
56
  }
55
57
  }
56
58
  </script>
@@ -63,12 +65,54 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
63
65
 
64
66
  The `mediabunny` line is needed only to export (the download button, `movie.render()`) or for a `video` layer; a page that only plays can leave it out and never fetches it (or call `useMediabunny(url)` instead, see [the API](docs/api.md#where-the-encoder-comes-from-usemediabunnysource)).
65
67
 
66
- Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.48.2/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.
68
+ Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.49.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.
67
69
 
68
- > **Using three.js?** Add two more entries (`three` and `pixi-effects/three`) to this importmap — see [Adding three.js](#adding-threejs-optional) below.
70
+ > **A 3D scene?** Add three more entries (`pixi-effects/3d`, `@pixi/3d`, `@pixi/3d/extras`) — see [Adding a 3D scene](#adding-a-3d-scene-optional) below. **Using three.js?** Add two more entries (`three` and `pixi-effects/three`) to this importmap — see [Adding three.js](#adding-threejs-optional) below.
69
71
  >
70
72
  > **Want the unreleased `main`?** Point the `pixi-effects*` entries at the GitHub Pages build instead: `https://yjmtmtk.github.io/pixi-effects/dist/index.js` (and `Controller.js`, `three.js`).
71
73
 
74
+ #### Adding a 3D scene (optional)
75
+
76
+ Add three entries to the importmap above (PixiJS 3D needs `pixi.js` 8.23.0 or later):
77
+
78
+ ```html
79
+ <script type="importmap">
80
+ {
81
+ "imports": {
82
+ "...": "(everything from the importmap above)",
83
+ "pixi-effects/3d": "https://cdn.jsdelivr.net/npm/pixi-effects@0.49.0/dist/3d.js",
84
+ "@pixi/3d": "https://cdn.jsdelivr.net/npm/@pixi/3d@0.1.0/dist/pixijs-3d.min.mjs",
85
+ "@pixi/3d/extras": "https://cdn.jsdelivr.net/npm/@pixi/3d@0.1.0/dist/pixijs-3d-extras.min.mjs"
86
+ }
87
+ }
88
+ </script>
89
+ ```
90
+
91
+ ```html
92
+ <script type="module">
93
+ import { Movie } from 'pixi-effects';
94
+ import { register3D, scene3d } from 'pixi-effects/3d'; // BEFORE movie.init()
95
+ import { Mesh3D, TorusKnotGeometry, Material3D, DirectionalLight } from '@pixi/3d';
96
+
97
+ register3D();
98
+
99
+ // ...inside composition.sequences:
100
+ scene3d({
101
+ type: 'scene3d', name: 'knot',
102
+ setup: ({ root, camera }) => {
103
+ const knot = new Mesh3D({ geometry: new TorusKnotGeometry(), material: new Material3D({ baseColor: 0x7fb4ff, roughness: 0.4 }) });
104
+ const sun = new DirectionalLight({ intensity: 3 }); sun.rotation.set(-0.8, 0.5, 0);
105
+ root.addChild(knot, sun);
106
+ camera.position.set(0, 0, 6); camera.lookAt({ x: 0, y: 0, z: 0 });
107
+ return { objects: { knot } }; // addressable from keyframes
108
+ },
109
+ keyframes: [{ at: 0, to: { 'scene3d.knot.rotation.y': Math.PI * 2 }, duration: 6 }],
110
+ })
111
+ </script>
112
+ ```
113
+
114
+ The scene is drawn inside PixiJS's renderer in the layer's place, so masks, filters, blend modes and opacity apply. `update(t)` must set everything from `t`. Full spec: [DSL reference § scene3d](./docs/dsl.md#scene3d) and [API reference](./docs/api.md#pixi-effects3d).
115
+
72
116
  #### Adding three.js (optional)
73
117
 
74
118
  Add two more entries to the importmap above: three.js itself, and the `pixi-effects/three` entry (and `three/addons/` if you load a glTF model with `threeGltf`).
@@ -80,7 +124,7 @@ Add two more entries to the importmap above: three.js itself, and the `pixi-effe
80
124
  "...": "(everything from the importmap above)",
81
125
  "three": "https://esm.sh/three@0.178.0",
82
126
  "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.178.0/examples/jsm/",
83
- "pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.48.2/dist/three.js"
127
+ "pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.49.0/dist/three.js"
84
128
  }
85
129
  }
86
130
  </script>
@@ -57,11 +57,14 @@
57
57
  <script type="importmap">
58
58
  {
59
59
  "imports": {
60
- "pixi.js": "https://cdn.jsdelivr.net/npm/pixi.js@8.22.0/dist/pixi.min.mjs",
60
+ "pixi.js": "https://cdn.jsdelivr.net/npm/pixi.js@8.23.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.48.2/dist/index.js",
63
- "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.48.2/dist/Controller.js",
64
- "pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.48.2/dist/present.js"
62
+ "pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.49.0/dist/index.js",
63
+ "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.49.0/dist/Controller.js",
64
+ "pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.49.0/dist/present.js",
65
+ "pixi-effects/3d": "https://cdn.jsdelivr.net/npm/pixi-effects@0.49.0/dist/3d.js",
66
+ "@pixi/3d": "https://cdn.jsdelivr.net/npm/@pixi/3d@0.1.0/dist/pixijs-3d.min.mjs",
67
+ "@pixi/3d/extras": "https://cdn.jsdelivr.net/npm/@pixi/3d@0.1.0/dist/pixijs-3d-extras.min.mjs"
65
68
  }
66
69
  }
67
70
  </script>
@@ -70,6 +73,7 @@
70
73
  import { Movie, kenBurns, withFade, wiggle, stagger, animateText, followPath, particles } from 'pixi-effects';
71
74
  import { Controller } from 'pixi-effects/controller';
72
75
  // A talk instead of a video: import { deck, Presenter } from 'pixi-effects/present'; use deck({ pages: [...] }) and new Presenter(movie, { canvas }) instead of the Controller.
76
+ // A 3D scene as a layer (inside the edit block, before init): const { register3D } = await import('pixi-effects/3d'); const { Mesh3D, CubeGeometry, Material3D, DirectionalLight } = await import('@pixi/3d'); register3D(); then { type: 'scene3d', setup, update }.
73
77
 
74
78
  // ===================== EDIT FROM HERE =====================
75
79
  const W = 1280, H = 720, FPS = 30, DURATION = 6;
@@ -520,7 +520,7 @@ export async function runCheck(opts, log = console.log) {
520
520
  const layoutProblems = rv.problems.filter(g => g.kind !== 'canvas'), canvasProblems = rv.problems.filter(g => g.kind === 'canvas');
521
521
  report.inspect = { checkedFrames: rv.frames, issues: layoutProblems, review: rv.review, canvas: canvasProblems.map(g => g.message), canvasLayers: rv.canvas ?? [] };
522
522
  if (layoutProblems.length) report.problems.push(`${layoutProblems.length} kind(s) of layout issue from movie.inspect`);
523
- if (canvasProblems.length) report.problems.push(`${canvasProblems.length} layer(s) (canvas / three) that do not draw the same picture for the same time (movie.canvasProblems)`);
523
+ if (canvasProblems.length) report.problems.push(`${canvasProblems.length} layer(s) (canvas / three / scene3d) that do not draw the same picture for the same time (movie.canvasProblems)`);
524
524
  // fonts: a web font whose file did not load is a problem (the library lists a layer with no available font for review)
525
525
  report.fonts = rv.fonts;
526
526
  if (rv.fonts.failed.length) report.problems.push(`${rv.fonts.failed.length} web font(s) failed to load: ${rv.fonts.failed.join(', ')} (the layers that use them are drawn in a fallback)`);
@@ -553,7 +553,7 @@ export async function runCheck(opts, log = console.log) {
553
553
  if (rows.some(([, px]) => px > 0)) w.depthBlur = Object.fromEntries(rows);
554
554
  // pick: what is under each --pick point at this moment (the frame is on screen after the snapshot above)
555
555
  if (opts.pick.length) {
556
- w.pick = await cdp.eval(`(${JSON.stringify(opts.pick)}).map(p => ({ x: p.x, y: p.y, hits: movie.pickAll(p.x, p.y).map(h => ({ name: h.name ?? h.path, path: h.path, kind: h.kind, object: h.three ? h.three.object : undefined })) }))`).catch(() => []);
556
+ w.pick = await cdp.eval(`(${JSON.stringify(opts.pick)}).map(p => ({ x: p.x, y: p.y, hits: movie.pickAll(p.x, p.y).map(h => ({ name: h.name ?? h.path, path: h.path, kind: h.kind, object: h.three ? h.three.object : h.scene3d ? h.scene3d.object : undefined })) }))`).catch(() => []);
557
557
  }
558
558
  // light: how lit each threeD layer is at this moment (left out when the composition has no light)
559
559
  const lit = await cdp.eval(`movie.inspect(${w.frame}).then(r => r.layers.filter(l => l.light !== undefined).map(l => [l.path, l.light]))`).catch(() => []);
@@ -895,7 +895,7 @@ export async function runSteps(cdp, steps, outDir, size, consoleLines, seenLogs)
895
895
  await sleep(300); // the page's handlers and rebuilds settle
896
896
  const after = await cdp.eval('JSON.parse(JSON.stringify(movie.vars || {}))').catch(() => ({}));
897
897
  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]]]));
898
- 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(() => []);
898
+ 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.kind === 'scene3d' ? h.path + ' (scene3d: ' + (h.scene3d && h.scene3d.object || '?') + ')' : h.path)`).catch(() => []);
899
899
  res.frame = await cdp.eval('movie.currentFrame').catch(() => null);
900
900
  res.playing = await cdp.eval('!!movie.isPlaying').catch(() => null);
901
901
  const now = await pageLogs();
@@ -930,8 +930,8 @@ function finish(report, outDir, log) {
930
930
  if (g.length > 8) L.push(` … ${g.length - 8} more (report.json)`);
931
931
  const cv = report.inspect.canvas ?? [];
932
932
  const rows = report.inspect.canvasLayers ?? [];
933
- if (cv.length) { L.push(` canvas ${cv.length} layer(s) (canvas / three) that do not draw the same picture for the same time:`); for (const m of cv.slice(0, 4)) L.push(` - ${m}`); }
934
- for (const kind of ['canvas', 'three']) { // silence must not be the only sign that the check ran
933
+ if (cv.length) { L.push(` canvas ${cv.length} layer(s) (canvas / three / scene3d) that do not draw the same picture for the same time:`); for (const m of cv.slice(0, 4)) L.push(` - ${m}`); }
934
+ for (const kind of ['canvas', 'three', 'scene3d']) { // silence must not be the only sign that the check ran
935
935
  const mine = rows.filter(r => (r.kind ?? 'canvas') === kind);
936
936
  if (!mine.length) continue;
937
937
  const good = mine.filter(r => r.ok), slow = [...mine].sort((a, b) => b.drawMs - a.drawMs)[0];
@@ -952,7 +952,7 @@ function finish(report, outDir, log) {
952
952
  }
953
953
  for (const w of (report.at ?? []).filter(x => x.pick)) {
954
954
  for (const p of w.pick) {
955
- const says = p.hits.length ? p.hits.map(h => h.kind === 'three' ? `${h.name} (three: ${h.object || '?'})` : h.name).join(', ') : 'nothing';
955
+ const says = p.hits.length ? p.hits.map(h => h.kind === 'three' || h.kind === 'scene3d' ? `${h.name} (${h.kind}: ${h.object || '?'})` : h.name).join(', ') : 'nothing';
956
956
  L.push(` pick ${w.label} (${p.x}, ${p.y}) — ${says}`);
957
957
  }
958
958
  }