retiam 2.0.1 → 2.0.3

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 (63) hide show
  1. package/THIRD_PARTY_NOTICES.md +3 -3
  2. package/agent/AGENTS.md +70 -0
  3. package/agent/CLAUDE.md +2 -0
  4. package/agent/skill/SKILL.md +114 -0
  5. package/agent/skill/examples/AreaLesson.ts +48 -0
  6. package/agent/skill/examples/ExplainMotion.ts +32 -0
  7. package/agent/skill/examples/NarratedMotion.ts +50 -0
  8. package/agent/skill/examples/SecantToTangent.ts +142 -0
  9. package/agent/skill/references/api.md +111 -0
  10. package/agent/skill/references/explainers.md +123 -0
  11. package/agent/skill/references/quality.md +151 -0
  12. package/agent/skill/references/styles.md +58 -0
  13. package/agent/skill/references/tts.md +166 -0
  14. package/agent/templates/explainer/lesson.json +25 -0
  15. package/agent/templates/explainer/review.json +6 -0
  16. package/agent/templates/explainer/styles.json +92 -0
  17. package/bin/agent-kit.js +26 -0
  18. package/bin/cli.js +24 -4
  19. package/bin/context.js +57 -0
  20. package/bin/init.js +71 -12
  21. package/bin/lesson-project.js +30 -0
  22. package/bin/lesson.js +195 -0
  23. package/bin/lint.js +6 -6
  24. package/bin/snapshot-runtime.js +185 -0
  25. package/bin/snapshot.js +32 -225
  26. package/dist/THIRD_PARTY_NOTICES.md +3 -3
  27. package/dist/UTIF-D6vx2UEX.min.js +1 -0
  28. package/dist/{UTIF-2Ear_0Uo.js → UTIF-mkffT70R.js} +1 -1
  29. package/dist/decompress-D7LzbvPD.min.js +15 -0
  30. package/dist/{index-B2sJBy4c.js → index-CdBPrYbr.js} +3830 -3795
  31. package/dist/index-jgFyrc2D.min.js +10472 -0
  32. package/dist/libs/explainer/example.d.ts +3 -0
  33. package/dist/libs/explainer/index.d.ts +5 -0
  34. package/dist/libs/explainer/index.js +135 -0
  35. package/dist/libs/explainer/plan.d.ts +12 -0
  36. package/dist/libs/explainer/plan.js +219 -0
  37. package/dist/libs/explainer/runtime.d.ts +24 -0
  38. package/dist/libs/explainer/styles.d.ts +3 -0
  39. package/dist/libs/explainer/types.d.ts +124 -0
  40. package/dist/libs/explainer/visuals.d.ts +18 -0
  41. package/dist/libs/tts/animations/Speak.d.ts +13 -7
  42. package/dist/libs/tts/components/TTSNarrationComponent.d.ts +9 -7
  43. package/dist/libs/tts/core/TTSCache.d.ts +3 -3
  44. package/dist/libs/tts/core/TTSClient.d.ts +3 -3
  45. package/dist/libs/tts/core/types.d.ts +48 -6
  46. package/dist/libs/tts/index.d.ts +2 -1
  47. package/dist/libs/tts/index.js +203 -1333
  48. package/dist/libs/tts/node/index.d.ts +10 -0
  49. package/dist/libs/tts/node/index.js +791 -0
  50. package/dist/libs/tts/providers/ElevenLabsTTSProvider.d.ts +30 -0
  51. package/dist/libs/tts/providers/ITTSProvider.d.ts +2 -2
  52. package/dist/libs/vite/index.d.ts +28 -0
  53. package/dist/libs/vite/index.js +158 -0
  54. package/dist/main-Cc4QQbSK.min.js +5939 -0
  55. package/dist/{main-D_G4uIju.js → main-DMu_5Qy7.js} +1 -1
  56. package/dist/msEdgeV2-D3w4Xsn-.js +642 -0
  57. package/dist/opentype.module-B-1cYAb-.min.js +1 -0
  58. package/dist/retiam.es.js +1 -1
  59. package/dist/retiam.es.min.js +1 -0
  60. package/dist/src/engine/animation/Animation.d.ts +1 -0
  61. package/dist/src/engine/entities/composites/text/mathjaxInterop.d.ts +6 -0
  62. package/package.json +15 -8
  63. package/readme.md +27 -7
@@ -0,0 +1,185 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import puppeteer from 'puppeteer';
4
+ import { createServer } from 'vite';
5
+
6
+ export function parseSnapshotOptions(args, projectRoot = process.cwd()) {
7
+ let file;
8
+ const options = { projectRoot, times: [1, 2.5], width: 1280, height: 720,
9
+ fps: 60, timeout: 120000, outDir: path.resolve(projectRoot, 'outputs/snapshot') };
10
+ const flags = { '-w': 'width', '--width': 'width', '-h': 'height', '--height': 'height',
11
+ '--fps': 'fps', '--timeout': 'timeout', '-o': 'outDir', '--out': 'outDir', '--times': 'times' };
12
+ for (let i = 0; i < args.length; i++) {
13
+ const arg = args[i];
14
+ if (arg === '--json') continue;
15
+ const key = flags[arg];
16
+ if (key) {
17
+ const value = args[++i];
18
+ if (value === undefined || value.startsWith('--')) throw new Error(`${arg} requires a value.`);
19
+ if (key === 'outDir') options.outDir = path.resolve(projectRoot, value);
20
+ else if (key === 'times') {
21
+ const values = value.split(',');
22
+ if (values.some(item => !item.trim() || !Number.isFinite(Number(item)) || Number(item) < 0)) {
23
+ throw new Error('--times must contain finite, nonnegative seconds.');
24
+ }
25
+ options.times = [...new Set(values.map(Number))].sort((a, b) => a - b);
26
+ } else {
27
+ const number = Number(value);
28
+ if (!Number.isSafeInteger(number) || number <= 0) throw new Error(`${arg} must be a positive integer.`);
29
+ options[key] = number;
30
+ }
31
+ } else if (arg.startsWith('-')) throw new Error(`Unknown option: ${arg}`);
32
+ else if (file) throw new Error('Provide one target scene file.');
33
+ else file = arg;
34
+ }
35
+ if (!file) throw new Error('No target scene file specified.');
36
+ const resolved = path.resolve(projectRoot, file);
37
+ if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) throw new Error(`Scene file not found: ${file}`);
38
+ const relative = path.relative(fs.realpathSync(projectRoot), fs.realpathSync(resolved));
39
+ if (relative.startsWith(`..${path.sep}`) || relative === '..' || path.isAbsolute(relative)) {
40
+ throw new Error('The scene must be inside the project directory.');
41
+ }
42
+ if (options.times.some(time => !Number.isFinite(time * 1000))) throw new Error('Snapshot times are too large.');
43
+ return { ...options, file, sceneUrl: '/' + relative.split(path.sep).map(encodeURIComponent).join('/') };
44
+ }
45
+
46
+ // Runs in the browser. No live render loop or project entrypoint is started.
47
+ async function snapshotRuntime(sceneUrl, width, height) {
48
+ const state = window.__retiamSnapshot = { ready: false, error: null, capture: null };
49
+ const fail = error => { state.error = error?.stack || error?.message || String(error); };
50
+ window.addEventListener('error', event => fail(event.error || event.message));
51
+ window.addEventListener('unhandledrejection', event => fail(event.reason));
52
+ try {
53
+ const [{ SceneComposer }, { default: SceneClass }] = await Promise.all([
54
+ import('retiam'), import(/* @vite-ignore */ sceneUrl),
55
+ ]);
56
+ if (typeof SceneClass !== 'function') throw new Error('The scene must default-export a Scene class.');
57
+ const canvas = document.querySelector('canvas');
58
+ canvas.width = width;
59
+ canvas.height = height;
60
+ const composer = new SceneComposer();
61
+ composer.enablePlayer(false);
62
+ await composer.init(canvas);
63
+ // Suppress invalidate() starting realtime playback, without pausing scene updates.
64
+ composer.PAUSED = true;
65
+ composer.device.addEventListener('uncapturederror', event => fail(event.error));
66
+ const scene = new SceneClass();
67
+ composer.add(scene);
68
+ Promise.resolve(scene.run()).catch(fail);
69
+
70
+ async function settleAssets() {
71
+ while (composer.pendingTasks.length || scene.pendingTasks.length) {
72
+ await Promise.all([...composer.pendingTasks.splice(0), ...scene.pendingTasks.splice(0)]);
73
+ }
74
+ if (state.error) throw new Error(state.error);
75
+ }
76
+
77
+ async function draw() {
78
+ await settleAssets();
79
+ composer.prepare();
80
+ composer.stateManager.uploadToGPU();
81
+ composer.submit(composer.getCurrentTexture());
82
+ await composer.device.queue.onSubmittedWorkDone();
83
+ if (state.error) throw new Error(state.error);
84
+ }
85
+
86
+ let currentTime = 0;
87
+ await settleAssets();
88
+ await composer.update(0, 0);
89
+ await draw();
90
+ state.capture = async (seconds, fps) => {
91
+ const target = seconds * 1000;
92
+ const step = 1000 / fps;
93
+ while (currentTime < target) {
94
+ const next = Math.min(target, currentTime + step);
95
+ await composer.update(next, next - currentTime);
96
+ currentTime = next;
97
+ // Submit each step, including compute/physics-dependent geometry.
98
+ await draw();
99
+ }
100
+ await draw();
101
+ // Present only requested frames; simulation steps need no wall-clock waits.
102
+ await new Promise(requestAnimationFrame);
103
+ if (state.error) throw new Error(state.error);
104
+ return currentTime / 1000;
105
+ };
106
+ state.ready = true;
107
+ } catch (error) { fail(error); }
108
+ }
109
+
110
+ export function snapshotHTML(options) {
111
+ const literal = JSON.stringify(options.sceneUrl).replaceAll('<', '\\u003c');
112
+ return `<!doctype html><html><head><link rel="icon" href="data:,"></head>
113
+ <body style="margin:0;overflow:hidden"><canvas style="display:block;width:100vw;height:100vh"></canvas>
114
+ <script type="module">(${snapshotRuntime.toString()})(${literal},${options.width},${options.height});</script></body></html>`;
115
+ }
116
+
117
+ export async function captureSnapshots(options, { signal } = {}) {
118
+ let server, browser;
119
+ const errors = [];
120
+ const route = '/__retiam_snapshot__.html';
121
+ // Vite and scene messages go to stderr so stdout remains one JSON document.
122
+ const logger = {
123
+ hasWarned: false, info: message => console.error(message), warn: message => console.error(message),
124
+ warnOnce: message => console.error(message), error: message => console.error(message),
125
+ clearScreen() {}, hasErrorLogged() { return false; },
126
+ };
127
+ const close = () => Promise.allSettled([browser?.close(), server?.close()]);
128
+ const abort = () => { void close(); };
129
+ signal?.addEventListener('abort', abort, { once: true });
130
+ try {
131
+ signal?.throwIfAborted();
132
+ server = await createServer({
133
+ root: options.projectRoot, logLevel: 'error', customLogger: logger,
134
+ server: { host: '127.0.0.1', port: 0, strictPort: false, open: false, hmr: false, watch: null },
135
+ plugins: [{ name: 'retiam-snapshot', configureServer(vite) {
136
+ vite.middlewares.use((request, response, next) => {
137
+ if (request.url?.split('?')[0] !== route) return next();
138
+ vite.transformIndexHtml(route, snapshotHTML(options)).then(html => {
139
+ response.setHeader('Content-Type', 'text/html');
140
+ response.end(html);
141
+ }).catch(next);
142
+ });
143
+ } }],
144
+ });
145
+ await server.listen();
146
+ signal?.throwIfAborted();
147
+ const launch = { headless: 'new', protocolTimeout: options.timeout,
148
+ args: ['--enable-unsafe-webgpu', '--no-sandbox', '--autoplay-policy=no-user-gesture-required'] };
149
+ if (process.env.PUPPETEER_EXECUTABLE_PATH) {
150
+ browser = await puppeteer.launch({ ...launch, executablePath: process.env.PUPPETEER_EXECUTABLE_PATH });
151
+ } else {
152
+ try { browser = await puppeteer.launch({ ...launch, channel: 'chrome' }); }
153
+ catch { browser = await puppeteer.launch(launch); }
154
+ }
155
+ signal?.throwIfAborted();
156
+ const page = await browser.newPage();
157
+ page.setDefaultTimeout(options.timeout);
158
+ page.on('console', message => console.error(`[Browser ${message.type()}] ${message.text()}`));
159
+ page.on('pageerror', error => errors.push(error.message));
160
+ page.on('requestfailed', request => console.error(`[Request failed] ${request.url()}: ${request.failure()?.errorText}`));
161
+ await page.setViewport({ width: options.width, height: options.height, deviceScaleFactor: 1 });
162
+ await page.goto(`http://127.0.0.1:${server.httpServer.address().port}${route}`, { waitUntil: 'domcontentloaded' });
163
+ await page.waitForFunction(() => window.__retiamSnapshot?.ready || window.__retiamSnapshot?.error);
164
+ const error = await page.evaluate(() => window.__retiamSnapshot.error);
165
+ if (error) throw new Error(error);
166
+ if (errors.length) throw new Error(errors.join('\n'));
167
+ fs.mkdirSync(options.outDir, { recursive: true });
168
+ const canvas = await page.$('canvas');
169
+ const base = path.basename(options.file, path.extname(options.file));
170
+ const snapshots = [];
171
+ for (const seconds of options.times) {
172
+ signal?.throwIfAborted();
173
+ const timeSeconds = await page.evaluate((time, fps) => window.__retiamSnapshot.capture(time, fps), seconds, options.fps);
174
+ if (errors.length) throw new Error(errors.join('\n'));
175
+ const filename = `snapshot_${base}_${seconds}s.png`;
176
+ const location = path.join(options.outDir, filename);
177
+ await canvas.screenshot({ path: location, type: 'png' });
178
+ snapshots.push({ timeSeconds, filename, path: location, width: options.width, height: options.height });
179
+ }
180
+ return { status: 'success', scene: options.file, fps: options.fps, snapshots };
181
+ } finally {
182
+ signal?.removeEventListener('abort', abort);
183
+ await close();
184
+ }
185
+ }
package/bin/snapshot.js CHANGED
@@ -1,231 +1,38 @@
1
1
  #!/usr/bin/env node
2
+ import { captureSnapshots, parseSnapshotOptions } from './snapshot-runtime.js';
2
3
 
3
- // =========================================================================
4
- // File: bin/snapshot.js - Retiam Agent-Native Visual Keyframe Snapshotter
5
- // =========================================================================
6
-
7
- import fs from 'fs';
8
- import path from 'path';
9
- import puppeteer from 'puppeteer';
10
- import { createServer } from 'vite';
11
-
12
- const projectRoot = process.cwd();
13
4
  const args = process.argv.slice(2);
14
-
15
- let targetFile = null;
16
- let times = [1.0, 2.5]; // default snapshot times in seconds
17
- let width = 1280;
18
- let height = 720;
19
- let outDir = path.resolve(projectRoot, 'outputs/snapshot');
20
- let jsonOutput = false;
21
-
22
- // Check for --help before parsing options
23
- const isHelp = args.includes('--help') || args.some((arg, idx) => arg === '-h' && (idx + 1 >= args.length || isNaN(parseInt(args[idx + 1], 10))));
24
- if (isHelp) {
25
- console.log(`
26
- 📸 Retiam WebGPU Engine - Keyframe Snapshotter
27
-
28
- Usage:
29
- npx retiam snapshot <path/to/Scene.ts> [options]
30
-
31
- Arguments:
32
- <file> Target scene TypeScript file (e.g. src/scenes/AStarScene.ts)
33
-
34
- Options:
35
- --times <sec,sec,...> Comma-separated list of timeline seconds to capture (default: 1.0, 2.5)
36
- -w, --width <px> Snapshot width in pixels (default: 1280)
37
- -h, --height <px> Snapshot height in pixels (default: 720)
38
- -o, --out <dir> Output directory for snapshot images (default: ./outputs/snapshot)
39
- --json Output machine-readable JSON for agents and automation
40
- --help Show this help message and exit
41
-
42
- Examples:
43
- npx retiam snapshot src/scenes/AStarScene.ts
44
- npx retiam snapshot src/scenes/AStarScene.ts --times 1.0,3.5,7.0 -w 1920 -h 1080
45
- npx retiam snapshot src/scenes/AStarScene.ts --times 5.0 --out ./outputs/snapshot --json
46
- `);
47
- process.exit(0);
48
- }
49
-
50
- for (let i = 0; i < args.length; i++) {
51
- if (args[i] === '--json') {
52
- jsonOutput = true;
53
- } else if (args[i] === '-w' || args[i] === '--width') {
54
- width = parseInt(args[++i], 10);
55
- } else if (args[i] === '-h' || args[i] === '--height') {
56
- height = parseInt(args[++i], 10);
57
- } else if (args[i] === '-o' || args[i] === '--out') {
58
- outDir = path.resolve(projectRoot, args[++i]);
59
- } else if (args[i] === '--times') {
60
- times = args[++i].split(',').map(s => parseFloat(s.trim())).filter(n => !isNaN(n)).sort((a, b) => a - b);
61
- } else if (!args[i].startsWith('-') && !targetFile) {
62
- targetFile = args[i];
5
+ const json = args.includes('--json');
6
+ if (args.includes('--help') || (args.includes('-h') && !/^\d+$/.test(args[args.indexOf('-h') + 1] || ''))) {
7
+ console.log(`Usage: retiam snapshot <scene.ts> [options]
8
+
9
+ Runs the named scene directly, advancing its timeline in fixed steps.
10
+
11
+ --times <sec,sec,...> Timeline seconds (default: 1,2.5)
12
+ -w, --width <px> Image width (default: 1280)
13
+ -h, --height <px> Image height (default: 720)
14
+ --fps <number> Timeline step rate (default: 60)
15
+ -o, --out <dir> PNG directory (default: outputs/snapshot)
16
+ --timeout <ms> Browser operation timeout (default: 120000)
17
+ --json One JSON result on stdout; diagnostics on stderr
18
+ --help Show this help
19
+
20
+ Example: retiam snapshot src/scenes/YourScene.ts --times 0,1.5,3 --json`);
21
+ } else {
22
+ if (json) {
23
+ // Project Vite plugins may log directly instead of using Vite's logger.
24
+ for (const method of ['log', 'info', 'debug']) console[method] = (...values) => console.error(...values);
63
25
  }
64
- }
65
-
66
- if (!targetFile) {
67
- if (jsonOutput) {
68
- console.log(JSON.stringify({ status: 'error', message: 'No target scene file specified.' }));
69
- } else {
70
- console.error('❌ Error: No target scene file specified.');
71
- console.log('Usage: npx retiam snapshot <path/to/Scene.ts> [--times 1.0,2.5,4.0] [--out ./outputs/snapshot] [--json]');
72
- console.log('Run "npx retiam snapshot --help" for more details.');
73
- }
74
- process.exit(1);
75
- }
76
-
77
- const resolvedScenePath = path.resolve(projectRoot, targetFile);
78
- if (!fs.existsSync(resolvedScenePath)) {
79
- if (jsonOutput) {
80
- console.log(JSON.stringify({ status: 'error', message: `Scene file not found: ${targetFile}` }));
81
- } else {
82
- console.error(`❌ Error: Scene file not found: ${resolvedScenePath}`);
83
- }
84
- process.exit(1);
85
- }
86
-
87
- if (!fs.existsSync(outDir)) {
88
- fs.mkdirSync(outDir, { recursive: true });
89
- }
90
-
91
- let viteServer = null;
92
- let browser = null;
93
-
94
- const cleanup = async () => {
95
- if (browser) try { await browser.close(); } catch (e) {}
96
- if (viteServer) try { await viteServer.close(); } catch (e) {}
97
- };
98
-
99
- process.on('SIGINT', async () => {
100
- await cleanup();
101
- process.exit(1);
102
- });
103
-
104
- (async () => {
26
+ const controller = new AbortController();
27
+ for (const signal of ['SIGINT', 'SIGTERM']) process.once(signal, () => controller.abort());
105
28
  try {
106
- if (!jsonOutput) {
107
- console.log(`\n📸 Starting Retiam Keyframe Snapshotter`);
108
- console.log(` Scene: ${targetFile}`);
109
- console.log(` Resolution: ${width}x${height}`);
110
- console.log(` Timestamps: ${times.map(t => `${t}s`).join(', ')}`);
111
- console.log(` Output Dir: ${outDir}\n`);
112
- console.log(`🚀 Starting local Vite server...`);
113
- }
114
-
115
- const userViteConfig = path.resolve(projectRoot, 'vite.config.ts');
116
- viteServer = await createServer({
117
- configFile: fs.existsSync(userViteConfig) ? userViteConfig : undefined,
118
- root: projectRoot,
119
- server: { port: 5199, strictPort: false },
120
- logLevel: 'error'
121
- });
122
- await viteServer.listen();
123
-
124
- const address = viteServer.httpServer.address();
125
- const actualPort = (address && typeof address === 'object') ? address.port : 5199;
126
-
127
- if (!jsonOutput) console.log(`🌐 Launching headless browser with WebGPU...`);
128
-
129
- const launchArgs = [
130
- '--enable-unsafe-webgpu',
131
- '--disable-web-security',
132
- '--no-sandbox',
133
- '--enable-experimental-web-platform-features',
134
- '--autoplay-policy=no-user-gesture-required'
135
- ];
136
-
137
- try {
138
- browser = await puppeteer.launch({
139
- headless: "new",
140
- args: launchArgs,
141
- channel: 'chrome'
142
- });
143
- } catch (e) {
144
- // Fallback to default bundled Chromium
145
- browser = await puppeteer.launch({
146
- headless: "new",
147
- args: launchArgs
148
- });
149
- }
150
-
151
- const page = await browser.newPage();
152
- await page.setViewport({ width, height, deviceScaleFactor: 1.0 });
153
-
154
- page.on('console', msg => {
155
- if (msg.type() === 'error') {
156
- console.error(`[BROWSER ERROR] ${msg.text()}`);
157
- } else {
158
- console.log(`[BROWSER LOG] ${msg.text()}`);
159
- }
160
- });
161
- page.on('requestfailed', request => {
162
- console.error(`[REQUEST FAILED] ${request.url()} - ${request.failure()?.errorText}`);
163
- });
164
- page.on('response', response => {
165
- if (response.status() >= 400) {
166
- console.error(`[HTTP ${response.status()}] ${response.url()}`);
167
- }
168
- });
169
-
170
- const url = `http://localhost:${actualPort}?file=${targetFile}`;
171
- await page.goto(url, { waitUntil: 'domcontentloaded' });
172
-
173
- // Wait for canvas to be present
174
- await page.waitForSelector('canvas', { timeout: 15000 });
175
-
176
- const baseSceneName = path.basename(targetFile, path.extname(targetFile));
177
- const snapshots = [];
178
-
179
- let lastTimeSec = 0;
180
- for (const timeSec of times) {
181
- const deltaMs = Math.max(0, (timeSec - lastTimeSec) * 1000);
182
- if (deltaMs > 0) {
183
- await new Promise(resolve => setTimeout(resolve, deltaMs));
184
- }
185
- lastTimeSec = timeSec;
186
-
187
- const filename = `snapshot_${baseSceneName}_${timeSec.toFixed(1)}s.png`;
188
- const filePath = path.join(outDir, filename);
189
-
190
- await page.screenshot({ path: filePath, type: 'png' });
191
-
192
- snapshots.push({
193
- timeSeconds: timeSec,
194
- filename,
195
- path: filePath,
196
- width,
197
- height
198
- });
199
-
200
- if (!jsonOutput) {
201
- console.log(` ✅ Captured snapshot at ${timeSec.toFixed(1)}s -> ${filename}`);
202
- }
203
- }
204
-
205
- await cleanup();
206
-
207
- if (jsonOutput) {
208
- console.log(JSON.stringify({
209
- status: 'success',
210
- scene: targetFile,
211
- snapshots
212
- }, null, 2));
213
- } else {
214
- console.log(`\n🎉 All keyframe snapshots successfully generated in: ${outDir}\n`);
215
- }
216
-
217
- process.exit(0);
218
- } catch (err) {
219
- await cleanup();
220
- if (jsonOutput) {
221
- console.log(JSON.stringify({
222
- status: 'error',
223
- message: err.message,
224
- stack: err.stack
225
- }));
226
- } else {
227
- console.error('\n❌ Snapshotting failed:', err);
228
- }
229
- process.exit(1);
29
+ const options = parseSnapshotOptions(args);
30
+ const result = await captureSnapshots(options, { signal: controller.signal });
31
+ if (json) process.stdout.write(JSON.stringify(result, null, 2) + '\n');
32
+ else console.log(`Captured ${result.snapshots.length} snapshots of ${result.scene}:\n${result.snapshots.map(item => ` ${item.timeSeconds}s: ${item.path}`).join('\n')}`);
33
+ } catch (error) {
34
+ if (json) process.stdout.write(JSON.stringify({ status: 'error', message: error.message }) + '\n');
35
+ else console.error(`Snapshot failed: ${error.message}`);
36
+ process.exitCode = 1;
230
37
  }
231
- })();
38
+ }
@@ -311,8 +311,8 @@ After dependency or asset changes, run `npm ci`, then
311
311
  for packages that omit license files, reviewed README excerpts, and asset provenance are recorded in
312
312
  `tools/third-party-sources.json` in the source repository. These internal maintenance records
313
313
  are not needed in distributions. Generation uses local files and checked-in originals;
314
- it does not execute package scripts or fetch new legal terms. Run
315
- `npm run licenses:check` to verify the committed input and file fingerprints.
316
- This command also runs before the library build.
314
+ it does not execute package scripts or fetch new legal terms. This is a
315
+ maintenance command for dependency, asset, or license changes; ordinary builds,
316
+ publishing, and package-version changes do not require regenerating the inventory.
317
317
 
318
318
  <!-- retiam-third-party-packages: ["@babel/code-frame@7.27.1","@babel/helper-validator-identifier@7.28.5","@dimforge/rapier2d-compat@0.20.0","@dimforge/rapier3d-compat@0.20.0","@petamoriken/float16@3.9.3","@puppeteer/browsers@1.9.1","@tensorflow/tfjs-backend-cpu@4.20.0","@tensorflow/tfjs-backend-webgl@4.20.0","@tensorflow/tfjs-backend-webgpu@4.20.0","@tensorflow/tfjs-converter@4.20.0","@tensorflow/tfjs-core@4.20.0","@tensorflow/tfjs-data@4.20.0","@tensorflow/tfjs-layers@4.20.0","@tensorflow/tfjs@4.20.0","@tootallnate/quickjs-emscripten@0.23.0","@types/dom-webcodecs@0.1.18","@types/estree@1.0.7","@types/libtess@1.2.0","@types/long@4.0.2","@types/node-fetch@2.6.13","@types/node@22.7.4","@types/offscreencanvas@2019.3.0","@types/offscreencanvas@2019.7.3","@types/prismjs@1.26.6","@types/seedrandom@2.4.34","@types/wicg-file-system-access@2020.9.8","@webgpu/types@0.1.38","@webgpu/types@0.1.40","@zappar/msdf-generator@1.2.4","agent-base@6.0.2","agent-base@7.1.3","ansi-regex@5.0.1","ansi-styles@4.3.0","argparse@1.0.10","argparse@2.0.1","as-number@1.0.0","ast-types@0.13.4","asynckit@0.4.0","axios@1.20.0","b4a@1.6.7","base64-js@1.5.1","basic-ftp@5.0.5","box3d-wasm@0.2.0","buffer-crc32@0.2.13","buffer@5.7.1","buffer@6.0.3","call-bind-apply-helpers@1.0.2","callsites@3.1.0","chalk@4.1.2","chromium-bidi@0.5.8","cli-progress@3.12.0","cliui@7.0.4","cliui@8.0.1","color-convert@2.0.1","color-name@1.1.4","combined-stream@1.0.8","comlink@4.4.2","commander@9.2.0","core-js@3.29.1","cosmiconfig@9.0.0","cross-fetch@4.0.0","data-uri-to-buffer@6.0.2","debug@4.3.4","degenerator@5.0.1","delayed-stream@1.0.0","devtools-protocol@0.0.1232444","dunder-proto@1.0.1","earcut@3.0.2","emoji-regex@8.0.0","end-of-stream@1.4.4","env-paths@2.2.1","error-ex@1.3.2","es-define-property@1.0.1","es-errors@1.3.0","es-object-atoms@1.1.2","es-set-tostringtag@2.1.0","esbuild@0.25.5","esbuild@0.28.1","escalade@3.2.0","escodegen@2.1.0","esm@3.2.25","esprima@4.0.1","estraverse@5.3.0","esutils@2.0.3","events@3.3.0","extract-zip@2.0.1","extrude-polyline@1.0.6","fast-fifo@1.3.2","fd-slicer@1.1.0","fdir@6.4.5","fdir@6.5.0","follow-redirects@1.16.0","form-data@4.0.6","function-bind@1.1.2","get-caller-file@2.0.5","get-intrinsic@1.3.0","get-proto@1.0.1","get-stream@5.2.0","get-uri@6.0.4","gl-vec2@1.3.0","gopd@1.2.0","hachure-fill@0.5.2","has-flag@4.0.0","has-symbols@1.1.0","has-tostringtag@1.0.2","hasown@2.0.4","http-proxy-agent@7.0.2","https-proxy-agent@5.0.1","https-proxy-agent@7.0.6","ieee754@1.2.1","import-fresh@3.3.1","inherits@2.0.4","ip-address@9.0.5","is-arrayish@0.2.1","is-fullwidth-code-point@3.0.0","isomorphic-ws@5.0.0","jolt-physics@1.1.0","js-tokens@4.0.0","js-yaml@4.1.0","jsbn@1.1.0","json-parse-even-better-errors@2.3.1","libtess@1.2.2","lines-and-columns@1.2.4","long@4.0.0","lru-cache@7.18.3","math-intrinsics@1.1.0","mathjax-full@3.2.2","matter-js@0.20.0","mhchemparser@4.2.1","mime-db@1.52.0","mime-types@2.1.35","mitt@3.0.1","mj-context-menu@0.6.1","mkdirp-classic@0.5.3","monaco-editor@0.45.0","mp4-muxer@5.2.2","ms@2.1.2","msedge-tts@2.0.8","nanoid@3.3.11","netmask@2.0.2","node-fetch@2.6.13","node-fetch@2.7.0","oidn-web@0.3.5","once@1.4.0","opentype.js@1.3.4","pac-proxy-agent@7.2.0","pac-resolver@7.0.1","pako@1.0.11","parent-module@1.0.1","parse-json@5.2.0","path-data-parser@0.1.0","pend@1.2.0","picocolors@1.1.1","picomatch@4.0.2","picomatch@4.0.3","planck@1.5.0","points-on-curve@0.2.0","points-on-path@0.2.1","polygon-clipping@0.15.7","polyline-miter-util@1.0.1","postcss@8.5.6","prismjs@1.30.0","progress@2.0.3","proxy-agent@6.3.1","proxy-from-env@1.1.0","proxy-from-env@2.1.0","pump@3.0.2","puppeteer-core@21.11.0","puppeteer@21.11.0","readable-stream@3.6.2","regenerator-runtime@0.13.11","require-directory@2.1.1","resolve-from@4.0.0","robust-predicates@3.0.3","rollup@4.41.1","roughjs@4.6.6","safe-buffer@5.2.1","seedrandom@3.0.5","smart-buffer@4.2.0","socks-proxy-agent@8.0.5","socks@2.8.5","source-map-js@1.2.1","source-map@0.6.1","speech-rule-engine@4.0.7","splaytree@3.2.3","sprintf-js@1.0.3","sprintf-js@1.1.3","stage-js@1.0.2","stream-browserify@3.0.0","streamx@2.22.1","string-width@4.2.3","string.prototype.codepointat@0.2.1","string_decoder@1.3.0","strip-ansi@6.0.1","supports-color@7.2.0","svg-parser@2.0.4","svg-pathdata@7.1.0","tar-fs@3.0.4","tar-stream@3.1.7","text-decoder@1.2.3","through@2.3.8","tiny-inflate@1.0.3","tinyglobby@0.2.15","tr46@0.0.3","tslib@2.8.1","tsx@4.22.4","typescript@5.4.5","unbzip2-stream@1.4.3","undici-types@6.19.8","urlpattern-polyfill@10.0.0","utif@2.0.1","util-deprecate@1.0.2","vite@6.3.6","webidl-conversions@3.0.1","webm-muxer@5.1.4","wgsl_reflect@1.2.3","whatwg-url@5.0.0","wicked-good-xpath@1.3.0","woff2-encoder@2.0.0","wrap-ansi@7.0.0","wrappy@1.0.2","ws@8.16.0","ws@8.22.0","xmldom-sre@0.1.31","y18n@5.0.8","yargs-parser@20.2.9","yargs-parser@21.1.1","yargs@16.2.0","yargs@17.7.2","yauzl@2.10.0","yerror@8.0.0"] -->