animader-cli 0.0.0-stage → 0.1.1

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 (71) hide show
  1. package/LICENSE +121 -0
  2. package/adapters/build-node.mjs +265 -0
  3. package/adapters/devserver.mjs +481 -0
  4. package/adapters/devsession.mjs +619 -0
  5. package/adapters/export-node.mjs +603 -0
  6. package/adapters/export-run.mjs +126 -0
  7. package/adapters/generations.mjs +183 -0
  8. package/adapters/http.mjs +294 -0
  9. package/adapters/launch.mjs +83 -0
  10. package/adapters/node-fs.mjs +80 -0
  11. package/adapters/pngine.mjs +87 -0
  12. package/adapters/snapshot.mjs +143 -0
  13. package/adapters/target.mjs +104 -0
  14. package/adapters/watch.mjs +355 -0
  15. package/adapters/webaudio.mjs +102 -0
  16. package/bin/animader.mjs +31 -0
  17. package/hello/join.js +46 -0
  18. package/hello/run.js +259 -0
  19. package/lib/build.mjs +126 -0
  20. package/lib/buildloop.mjs +254 -0
  21. package/lib/contract.mjs +371 -0
  22. package/lib/errors.mjs +51 -0
  23. package/lib/export.mjs +445 -0
  24. package/lib/new.mjs +304 -0
  25. package/lib/pipeline.mjs +247 -0
  26. package/lib/receipt.mjs +331 -0
  27. package/lib/rmtree.mjs +49 -0
  28. package/lib/routes.mjs +170 -0
  29. package/lib/score.mjs +434 -0
  30. package/lib/show.mjs +458 -0
  31. package/lib/soundtrack.mjs +67 -0
  32. package/lib/vendor.mjs +99 -0
  33. package/lib/wav.mjs +197 -0
  34. package/package.json +47 -4
  35. package/player/boot.mjs +792 -0
  36. package/player/controller.mjs +366 -0
  37. package/player/devchannel.mjs +306 -0
  38. package/player/index.html +35 -0
  39. package/player/page.mjs +134 -0
  40. package/player/player.css +393 -0
  41. package/player/release.mjs +76 -0
  42. package/src/commands/build.mjs +201 -0
  43. package/src/commands/dev.mjs +214 -0
  44. package/src/commands/doctor.mjs +348 -0
  45. package/src/commands/export.mjs +158 -0
  46. package/src/commands/new.mjs +164 -0
  47. package/src/commands/project.mjs +46 -0
  48. package/src/config.mjs +110 -0
  49. package/src/router.mjs +149 -0
  50. package/src/toolchain.mjs +691 -0
  51. package/starters/circle/README.md +50 -0
  52. package/starters/circle/animader.json +4 -0
  53. package/starters/circle/gitignore +9 -0
  54. package/starters/circle/kernels/circle.wgsl +32 -0
  55. package/starters/circle/program.sjon +34 -0
  56. package/starters/circle/timeline.piece.sjon +26 -0
  57. package/starters/ember/bake/spans.bin +0 -0
  58. package/starters/ember/kernels/curl.wgsl +10 -0
  59. package/starters/ember/kernels/ember.wgsl +10 -0
  60. package/starters/ember/kernels/hash.wgsl +12 -0
  61. package/starters/ember/kernels/simplex.wgsl +27 -0
  62. package/starters/ember/program.sjon +328 -0
  63. package/starters/ember/timeline.piece.sjon +34 -0
  64. package/toolchain/licenses/animader.txt +121 -0
  65. package/toolchain/licenses/esbuild.txt +21 -0
  66. package/toolchain/licenses/pacer.txt +121 -0
  67. package/toolchain/licenses/pngine.txt +121 -0
  68. package/toolchain/manifest.json +212 -0
  69. package/toolchain/prepare.mjs +446 -0
  70. package/toolchain/uniform-type-name.js +184 -0
  71. package/README.md +0 -3
package/lib/score.mjs ADDED
@@ -0,0 +1,434 @@
1
+ // The demo's portable score/run composition (M1 slice 2). One load walks
2
+ // the real stages a show needs before it can be READY: the piece boundary
3
+ // derives the score from the document's inline roots (`PieceHost.score`,
4
+ // never a second reader), the target join is the compiled program's own
5
+ // reflection (`buildJoin` by import, so a target the program does not
6
+ // declare is refused by the compile instead of drifting with a hand
7
+ // list), pacer compiles the derived bytes against exactly that one
8
+ // visual registry, and the compiled metadata is audited so automation
9
+ // the hosted buses cannot take is refused — never silently dropped.
10
+ //
11
+ // Every producer is an injected port (`factories.instantiatePieceHost` /
12
+ // `factories.loadPacer`, shaped like `PieceHost.instantiate` and
13
+ // `Pacer.load`), so this module carries no `node:` import, no process
14
+ // state and no DOM, and the same decisions run in a page, a headless
15
+ // test or a future host. Failures are `ShowError`s: a stable `code`, the
16
+ // `stage` that failed, the `document` name and the producer's own
17
+ // `diagnostics`, so a caller reports causes instead of guessing them.
18
+ //
19
+ // Ownership: stage order, the single-registry join policy, the
20
+ // never-drop audit, the structured error shape. WASM bytes, reflection
21
+ // and IO belong to the host adapters.
22
+
23
+ import { buildJoin } from '../hello/join.js';
24
+
25
+ /** The stages `loadShow` walks, in order. */
26
+ export const SHOW_STAGES = Object.freeze([
27
+ 'piece_host',
28
+ 'derive_score',
29
+ 'reflect',
30
+ 'compile',
31
+ 'audit',
32
+ ]);
33
+
34
+ /**
35
+ * One structured show failure. `code` names the decision, `stage` the
36
+ * step that failed, `document` the piece document's name and
37
+ * `diagnostics` the producer's own answer (pacer 'PACD' records for a
38
+ * refused compile; show audit records for unhosted automation).
39
+ */
40
+ export class ShowError extends Error {
41
+ /**
42
+ * @param {string} code
43
+ * @param {string} message
44
+ * @param {{stage?: string, document?: string, diagnostics?: Array<object>, cause?: unknown}} [detail]
45
+ */
46
+ constructor(code, message, { stage = '', document = '', diagnostics = [], cause } = {}) {
47
+ super(message, cause === undefined ? undefined : { cause });
48
+ this.name = 'ShowError';
49
+ this.code = code;
50
+ this.stage = stage;
51
+ this.document = document;
52
+ this.diagnostics = diagnostics;
53
+ }
54
+ }
55
+
56
+ /** The one failure shape for a missing or malformed port. */
57
+ function portError(loader, what) {
58
+ return new TypeError(`${loader} needs ${what}`);
59
+ }
60
+
61
+ /** `name`, or a sentence for a target the score declared but never named for the host. */
62
+ function named(name) {
63
+ return name === null || name === undefined ? 'an unnamed declared target' : `'${name}'`;
64
+ }
65
+
66
+ /** The diagnostics' own messages, for the error text. */
67
+ function messagesOf(diagnostics) {
68
+ const messages = (Array.isArray(diagnostics) ? diagnostics : [])
69
+ .filter((entry) => entry && typeof entry.message === 'string')
70
+ .map((entry) => entry.message);
71
+ return messages.length > 0 ? messages.join('; ') : 'no diagnostics were answered';
72
+ }
73
+
74
+ /**
75
+ * The reflection stage shared by both score spellings: validate the
76
+ * compiled program's own table, join exactly the targets it can carry,
77
+ * then refuse an empty target list before any compile runs.
78
+ */
79
+ export function reflectedJoin(reflection, name) {
80
+ // The join is the reflection's own target list, in its table order; an
81
+ // empty one means the program declares nothing a score can address. A
82
+ // malformed field is a named reflection failure, never a guessed target.
83
+ for (const [fieldName, field] of Object.entries(reflection)) {
84
+ if (!field || typeof field !== 'object' || typeof field.type !== 'string' || field.type === '') {
85
+ throw new ShowError(
86
+ 'bad_reflection',
87
+ `${name}'s reflected field '${fieldName}' has no WGSL type, so the score join cannot be built`,
88
+ { stage: 'reflect', document: name },
89
+ );
90
+ }
91
+ if (field.elemCount !== undefined && !Number.isInteger(field.elemCount)) {
92
+ throw new ShowError(
93
+ 'bad_reflection',
94
+ `${name}'s reflected field '${fieldName}' has a non-integer elemCount (${String(field.elemCount)})`,
95
+ { stage: 'reflect', document: name },
96
+ );
97
+ }
98
+ }
99
+ let join;
100
+ try {
101
+ join = buildJoin(reflection);
102
+ } catch (cause) {
103
+ throw new ShowError(
104
+ 'bad_reflection',
105
+ `${name}'s reflected fields could not be joined: ${cause?.message ?? cause}`,
106
+ { stage: 'reflect', document: name, cause },
107
+ );
108
+ }
109
+ if (join.length === 0) {
110
+ throw new ShowError(
111
+ 'no_score_targets',
112
+ `the compiled program reflects no score targets for ${name}, so there is no registry to join`,
113
+ { stage: 'reflect', document: name },
114
+ );
115
+ }
116
+ return join;
117
+ }
118
+
119
+ /**
120
+ * The compile and audit stages shared by both score spellings. The score
121
+ * bytes are already final here: `loadShow` derived them, `loadDerivedShow`
122
+ * received them from the export. Returns exactly the public load result.
123
+ */
124
+ async function compileShow({ name, score, reflection, pacerWasm, factories }) {
125
+ const join = reflectedJoin(reflection, name);
126
+
127
+ let driver;
128
+ try {
129
+ // Keep the receiver the old load handed to its port: callers may pass a
130
+ // method rather than a bare function, and the compile stage must not
131
+ // change its `this`.
132
+ driver = await factories.loadPacer(pacerWasm, score, { registries: [join] });
133
+ } catch (cause) {
134
+ throw new ShowError(
135
+ 'pacer_load_failed',
136
+ `the score compile failed for ${name}: ${cause?.message ?? cause}`,
137
+ { stage: 'compile', document: name, cause },
138
+ );
139
+ }
140
+ if (!driver.ok) {
141
+ throw new ShowError(
142
+ 'score_refused',
143
+ `${name}'s derived score was refused by the compile: ${messagesOf(driver.diagnostics)}`,
144
+ { stage: 'compile', document: name, diagnostics: Array.isArray(driver.diagnostics) ? driver.diagnostics : [] },
145
+ );
146
+ }
147
+
148
+ // The metadata is the driver's own public answer ('PACM' through the
149
+ // `meta` getter), read once here — never reconstructed or guessed.
150
+ const meta = driver.meta;
151
+ if (meta === null || meta === undefined) {
152
+ throw new ShowError(
153
+ 'metadata_unavailable',
154
+ `the compiled driver answered no metadata for ${name}`,
155
+ { stage: 'audit', document: name },
156
+ );
157
+ }
158
+
159
+ auditMeta(name, meta);
160
+
161
+ return { documentName: name, score, join, driver, meta };
162
+ }
163
+
164
+ /**
165
+ * The never-drop audit shared by both score spellings. This show hosts one
166
+ * registry (the visual bus), so a track or control the metadata places on
167
+ * any other bus — or a declared-only target with no host name, which a
168
+ * by-name fold cannot key — would be dropped by the route pair's own rules.
169
+ * Refuse it with the metadata's own names; a compile's unknown-target
170
+ * diagnostics have already refused everything addressable before this point.
171
+ */
172
+ function auditMeta(name, meta) {
173
+ const buses = 1;
174
+ const audit = [];
175
+ for (const track of meta.tracks ?? []) {
176
+ if (track.name === null || track.name === undefined || track.registry >= buses) {
177
+ audit.push({
178
+ source: 'show',
179
+ severity: 'error',
180
+ code: 0,
181
+ start: track.span?.start ?? 0,
182
+ end: track.span?.end ?? 0,
183
+ message: `track ${named(track.name)} automates registry ${track.registry}, which this show does not host`,
184
+ });
185
+ }
186
+ }
187
+ for (const control of meta.controls ?? []) {
188
+ if (control.registry >= buses) {
189
+ audit.push({
190
+ source: 'show',
191
+ severity: 'error',
192
+ code: 0,
193
+ start: control.valueSpan?.start ?? 0,
194
+ end: control.valueSpan?.end ?? 0,
195
+ message: `control ${named(control.name ?? control.id)} pins registry ${control.registry}, which this show does not host`,
196
+ });
197
+ }
198
+ }
199
+ if (audit.length > 0) {
200
+ throw new ShowError(
201
+ 'unsupported_automation',
202
+ `${name} automates targets outside the ${buses} hosted registrie${buses === 1 ? '' : 's'} (${messagesOf(audit)}); automation is never dropped`,
203
+ { stage: 'audit', document: name, diagnostics: audit },
204
+ );
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Compose one show's score run from injected producers.
210
+ *
211
+ * @param {object} options
212
+ * @param {{name: string, text: string}} options.document
213
+ * the piece document (the inline-roots aggregate) and its name, kept
214
+ * by every failure.
215
+ * @param {object} options.dep
216
+ * @param {WebAssembly.Module|BufferSource} options.dep.pieceWasm animader.wasm.
217
+ * @param {WebAssembly.Module|BufferSource} options.dep.pacerWasm pacer.wasm.
218
+ * @param {Object<string, {type: string, elemCount: number}>} options.dep.reflection
219
+ * the compiled program's reflected fields, as its own public
220
+ * reflection reports them (`getUniforms`'s parsed answer).
221
+ * @param {object} options.factories
222
+ * @param {(wasm: WebAssembly.Module|BufferSource) => Promise<{score(text: string): string}>} options.factories.instantiatePieceHost
223
+ * @param {(wasm: WebAssembly.Module|BufferSource, score: string, options: {registries: Array<Array<{name: string, ncomp: number}>>}) => Promise<{ok: boolean, diagnostics: Array<object>, meta: object}>} options.factories.loadPacer
224
+ * @returns {Promise<{documentName: string, score: string, join: Array<{name: string, ncomp: number}>, driver: object, meta: object}>}
225
+ * the derived score, the reflected join, the compiled driver and the
226
+ * driver's own public metadata. A failed load throws before any of
227
+ * this is handed out, so nothing half-loaded escapes to be reused.
228
+ */
229
+ export async function loadShow({ document, dep, factories }) {
230
+ if (!document
231
+ || typeof document.name !== 'string'
232
+ || document.name === ''
233
+ || typeof document.text !== 'string'
234
+ || document.text === '') {
235
+ throw portError('loadShow', 'document.name and a non-empty document.text (the inline-roots aggregate)');
236
+ }
237
+ if (!factories || typeof factories.instantiatePieceHost !== 'function' || typeof factories.loadPacer !== 'function') {
238
+ throw portError('loadShow', 'factories.instantiatePieceHost and factories.loadPacer (the PieceHost.instantiate and Pacer.load shapes)');
239
+ }
240
+ if (!dep || dep.pieceWasm === undefined || dep.pieceWasm === null
241
+ || dep.pacerWasm === undefined || dep.pacerWasm === null
242
+ || typeof dep.reflection !== 'object' || dep.reflection === null) {
243
+ throw portError('loadShow', 'dep.pieceWasm, dep.pacerWasm and dep.reflection (the compiled program\'s reflected fields)');
244
+ }
245
+
246
+ const name = document.name;
247
+
248
+ let host;
249
+ try {
250
+ host = await factories.instantiatePieceHost(dep.pieceWasm);
251
+ } catch (cause) {
252
+ throw new ShowError(
253
+ 'piece_host_failed',
254
+ `the piece boundary could not be instantiated for ${name}: ${cause?.message ?? cause}`,
255
+ { stage: 'piece_host', document: name, cause },
256
+ );
257
+ }
258
+
259
+ let score;
260
+ try {
261
+ score = host.score(document.text);
262
+ } catch (cause) {
263
+ throw new ShowError(
264
+ 'derive_failed',
265
+ `the piece boundary refused ${name}: ${cause?.message ?? cause}`,
266
+ { stage: 'derive_score', document: name, cause },
267
+ );
268
+ }
269
+ if (typeof score !== 'string' || score === '') {
270
+ throw new ShowError(
271
+ 'empty_score',
272
+ `the piece boundary answered an empty score for ${name}`,
273
+ { stage: 'derive_score', document: name },
274
+ );
275
+ }
276
+
277
+ return compileShow({
278
+ name,
279
+ score,
280
+ reflection: dep.reflection,
281
+ pacerWasm: dep.pacerWasm,
282
+ factories,
283
+ });
284
+ }
285
+
286
+ /**
287
+ * Compose the same show run from a score already derived at export time.
288
+ * The release page has no PieceHost and no compiler: the bytes it compiles
289
+ * are the export's actual `PieceHost.score` answer, and the reflection is
290
+ * the prepared program's own live `getUniforms` table (or, in Node export
291
+ * verification, the same table parsed from the baked payload).
292
+ *
293
+ * @param {object} options
294
+ * @param {{name: string, text: string}} options.document
295
+ * the derived score document and the name every failure keeps.
296
+ * @param {object} options.dep
297
+ * @param {WebAssembly.Module|BufferSource} options.dep.pacerWasm pacer.wasm.
298
+ * @param {Object<string, {type: string, elemCount: number}>} options.dep.reflection
299
+ * the compiled program's reflected fields.
300
+ * @param {object} options.factories
301
+ * @param {(wasm: WebAssembly.Module|BufferSource, score: string, options: {registries: Array<Array<{name: string, ncomp: number}>>}) => Promise<{ok: boolean, diagnostics: Array<object>, meta: object}>} options.factories.loadPacer
302
+ * @returns {Promise<{documentName: string, score: string, join: Array<{name: string, ncomp: number}>, driver: object, meta: object}>}
303
+ * the same shape `loadShow` answers, so the page's player wiring is
304
+ * identical on both spellings.
305
+ */
306
+ export async function loadDerivedShow({ document, dep, factories }) {
307
+ if (!document
308
+ || typeof document.name !== 'string'
309
+ || document.name === ''
310
+ || typeof document.text !== 'string'
311
+ || document.text === '') {
312
+ throw portError('loadDerivedShow', 'document.name and a non-empty document.text (the prederived score)');
313
+ }
314
+ if (!factories || typeof factories.loadPacer !== 'function') {
315
+ throw portError('loadDerivedShow', 'factories.loadPacer (the Pacer.load shape)');
316
+ }
317
+ if (!dep
318
+ || dep.pacerWasm === undefined || dep.pacerWasm === null
319
+ || typeof dep.reflection !== 'object' || dep.reflection === null) {
320
+ throw portError('loadDerivedShow', 'dep.pacerWasm and dep.reflection (the compiled program\'s reflected fields)');
321
+ }
322
+
323
+ const name = document.name;
324
+ return compileShow({
325
+ name,
326
+ score: document.text,
327
+ reflection: dep.reflection,
328
+ pacerWasm: dep.pacerWasm,
329
+ factories,
330
+ });
331
+ }
332
+
333
+ /** The packed container's four-byte magic, as bytes 'P' 'A' 'C' 'B'. */
334
+ const PACB_MAGIC = [0x50, 0x41, 0x43, 0x42];
335
+
336
+ /** A non-copying Uint8Array view of a Uint8Array/BufferSource, or null. */
337
+ function packedView(value) {
338
+ if (value instanceof Uint8Array) return value;
339
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
340
+ return null;
341
+ }
342
+
343
+ /**
344
+ * Compose the release show from a packed 'PACB' score. There is no source
345
+ * text and no compiler here: the export's real `pacer pack` answer is
346
+ * loaded by the playback-only runtime against the prepared program's
347
+ * reflected join. The join, metadata and never-drop audit are the same
348
+ * decisions the text paths make, so the page wiring stays identical.
349
+ *
350
+ * @param {object} options
351
+ * @param {{name: string, bytes: Uint8Array|ArrayBuffer}} options.document
352
+ * the packed score and the name every failure keeps.
353
+ * @param {object} options.dep
354
+ * @param {WebAssembly.Module|BufferSource} options.dep.pacerRtWasm
355
+ * pacer-rt.wasm.
356
+ * @param {Object<string, {type: string, elemCount: number}>} options.dep.reflection
357
+ * the compiled program's reflected fields.
358
+ * @param {object} options.factories
359
+ * @param {(wasm: WebAssembly.Module|BufferSource, pacb: Uint8Array|ArrayBuffer, options: {registries: Array<Array<{name: string, ncomp: number}>>}) => Promise<{ok: boolean, status: number, reason: number, meta: object}>} options.factories.loadPacerRt
360
+ * the `PacerRt.load` shape.
361
+ * @returns {Promise<{documentName: string, score: Uint8Array, scoreBytes: number, join: Array<{name: string, ncomp: number}>, driver: object, meta: object}>}
362
+ * the packed bytes, the reflected join, the playback driver and the
363
+ * driver's own public metadata.
364
+ */
365
+ export async function loadPackedShow({ document, dep, factories }) {
366
+ const name = document && typeof document.name === 'string' && document.name !== ''
367
+ ? document.name
368
+ : 'score.pacb';
369
+ const bytes = packedView(document?.bytes);
370
+ const magicOk = bytes !== null
371
+ && bytes.byteLength >= PACB_MAGIC.length
372
+ && PACB_MAGIC.every((byte, index) => bytes[index] === byte);
373
+ if (!magicOk) {
374
+ throw new ShowError(
375
+ 'bad_packed_document',
376
+ `${name} is not a 'PACB' packed score (a non-empty Uint8Array starting with the four-byte 'PACB' magic is required)`,
377
+ { stage: 'load', document: name },
378
+ );
379
+ }
380
+ if (!factories || typeof factories.loadPacerRt !== 'function') {
381
+ throw portError('loadPackedShow', 'factories.loadPacerRt (the PacerRt.load shape)');
382
+ }
383
+ if (!dep
384
+ || dep.pacerRtWasm === undefined || dep.pacerRtWasm === null
385
+ || typeof dep.reflection !== 'object' || dep.reflection === null) {
386
+ throw portError('loadPackedShow', 'dep.pacerRtWasm and dep.reflection (the compiled program\'s reflected fields)');
387
+ }
388
+
389
+ const join = reflectedJoin(dep.reflection, name);
390
+
391
+ let driver;
392
+ try {
393
+ driver = await factories.loadPacerRt(dep.pacerRtWasm, bytes, { registries: [join] });
394
+ } catch (cause) {
395
+ throw new ShowError(
396
+ 'pacer_load_failed',
397
+ `the packed score could not be loaded for ${name}: ${cause?.message ?? cause}`,
398
+ { stage: 'compile', document: name, cause },
399
+ );
400
+ }
401
+ if (!driver.ok) {
402
+ const status = Number.isInteger(driver.status) ? driver.status : -1;
403
+ const reason = Number.isInteger(driver.reason) ? driver.reason : -1;
404
+ throw new ShowError(
405
+ 'score_refused',
406
+ `${name}'s packed score was refused by the playback runtime (status ${status}, reason ${reason})`,
407
+ {
408
+ stage: 'compile',
409
+ document: name,
410
+ diagnostics: Array.isArray(driver.diagnostics) ? driver.diagnostics : [],
411
+ },
412
+ );
413
+ }
414
+
415
+ const meta = driver.meta;
416
+ if (meta === null || meta === undefined) {
417
+ throw new ShowError(
418
+ 'metadata_unavailable',
419
+ `the packed driver answered no metadata for ${name}`,
420
+ { stage: 'audit', document: name },
421
+ );
422
+ }
423
+
424
+ auditMeta(name, meta);
425
+
426
+ return {
427
+ documentName: name,
428
+ score: bytes,
429
+ scoreBytes: bytes.byteLength,
430
+ join,
431
+ driver,
432
+ meta,
433
+ };
434
+ }