@flatkit/compiler 0.37.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/analysis.js CHANGED
@@ -27,8 +27,8 @@ import {
27
27
  scopeRegions,
28
28
  splitScopeProgram,
29
29
  wrapMetrics
30
- } from "./chunk-43MGBTUK.js";
31
- import "./chunk-KXKX2RMX.js";
30
+ } from "./chunk-RQKXBHHM.js";
31
+ import "./chunk-BPX2MOW2.js";
32
32
  export {
33
33
  allScopeVariables,
34
34
  applyFixes,
@@ -12,9 +12,9 @@ function resolveRefs(layers, byName) {
12
12
  }
13
13
  function compileFlatpack(programSrc, assetSrcs = [], media = {}) {
14
14
  const libs = assetSrcs.map((src) => parseFlatLib(src));
15
- const symbols = libs.flatMap((l) => l.symbols);
16
- const folders = libs.flatMap((l) => l.folders);
17
15
  const prog = parseProgramFull(programSrc);
16
+ const symbols = [...libs.flatMap((l) => l.symbols), ...prog.symbols ?? []];
17
+ const folders = libs.flatMap((l) => l.folders);
18
18
  const byName = new Map(symbols.map((s) => [s.name, s.id]));
19
19
  symbols.forEach((s) => resolveRefs(s.layers, byName));
20
20
  resolveRefs(prog.layers, byName);
@@ -45,4 +45,4 @@ export {
45
45
  compileFlatpack,
46
46
  packToJSON
47
47
  };
48
- //# sourceMappingURL=chunk-KXKX2RMX.js.map
48
+ //# sourceMappingURL=chunk-BPX2MOW2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/compile.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// compile.ts — the \"modern SWF\" compiler (RFC, step P2).\n//\n// Takes the PROGRAM (`.flatink`) + the ASSETS (`.flat`) and produces a resolved `Doc` —\n// the \".flatpack\" v1 (= the baked doc the player already plays). Steps:\n// 1. parse the assets → symbols;\n// 2. parse the program → composition + behavior (`@Name` refs);\n// 3. RESOLVE refs by name (instances → symbol id), across ALL libs;\n// 4. assemble the Doc.\n//\n// v1: no binary optimization nor media embedding (P2b). Pure, no DOM.\n// ─────────────────────────────────────────────────────────────────────────────\nimport type { Doc, Item, Layer } from '@flatkit/types'\nimport { parseFlatLib, parseProgramFull } from '@flatkit/engine/flatFormat'\nimport { isGroup, isInstance } from '@flatkit/engine/layers'\n\n/** Resolves `symbolId: '@Name'` instances into real ids (recursive, across groups). */\nfunction resolveRefs(layers: Layer[], byName: Map<string, string>): void {\n const walk = (items: Item[]) => {\n for (const it of items) {\n if (isInstance(it) && it.symbolId.startsWith('@')) it.symbolId = byName.get(it.symbolId.slice(1)) ?? it.symbolId\n if (isGroup(it)) it.layers.forEach((l) => walk(l.items))\n }\n }\n layers.forEach((l) => walk(l.items))\n}\n\n/** Source of a media (base64 data-URI) by declared path (`asset … \"path\" …`). */\nexport type MediaMap = Record<string, { mime: string; data: string }>\n\n/**\n * Compile a `.flatink` program + its `.flat` assets → playable `Doc` (the \".flatpack\").\n * `assetSrcs` = the text of each `.flat` lib. `media` = the content of the referenced media files\n * (by path). Symbol refs (by name) are resolved across the whole set of libs; declared media\n * are EMBEDDED (path → data-URI).\n */\nexport function compileFlatpack(programSrc: string, assetSrcs: string[] = [], media: MediaMap = {}): Doc {\n const libs = assetSrcs.map((src) => parseFlatLib(src))\n const prog = parseProgramFull(programSrc)\n // The program's OWN symbols (`symbol \"X\" { … }` written in the `.flatink`) come last: on a name both\n // declare, the program's wins over the library's.\n const symbols = [...libs.flatMap((l) => l.symbols), ...(prog.symbols ?? [])]\n const folders = libs.flatMap((l) => l.folders) // library folders (organization)\n const byName = new Map(symbols.map((s) => [s.name, s.id]))\n symbols.forEach((s) => resolveRefs(s.layers, byName)) // cross-lib refs\n resolveRefs(prog.layers, byName) // program refs\n\n // Embed declared media (data = path → provided data-URI). Missing = left as is.\n const assets = (prog.assets ?? []).map((a) => {\n const m = media[a.data]\n const r = m ? { ...a, mime: m.mime, data: m.data } : a\n // A font without an explicit family defaults to its declared id — which is exactly what text targets via\n // `font \"<id>\"`. Gives consumers an explicit `family` instead of relying on a `family || id` fallback.\n return r.kind === 'font' && !r.family ? { ...r, family: r.id } : r\n })\n\n return {\n width: prog.width,\n height: prog.height,\n ...(prog.background ? { background: prog.background } : {}),\n symbols,\n ...(folders.length ? { folders } : {}),\n layers: prog.layers,\n ...(prog.variables && Object.keys(prog.variables).length ? { variables: prog.variables } : {}),\n ...(prog.imports?.length ? { imports: prog.imports } : {}),\n ...(prog.functions?.length ? { functions: prog.functions } : {}),\n timeline: prog.timeline ?? { fps: 24, durationFrames: 60, tracks: [] },\n ...(prog.interactions?.length ? { interactions: prog.interactions } : {}),\n ...(prog.interactors?.length ? { interactors: prog.interactors } : {}),\n ...(assets.length ? { assets } : {}),\n }\n}\n\n/** Serialize the `.flatpack` v1 (JSON of the compiled Doc). */\nexport const packToJSON = (doc: Doc): string => JSON.stringify(doc)\n"],"mappings":";AAaA,SAAS,cAAc,wBAAwB;AAC/C,SAAS,SAAS,kBAAkB;AAGpC,SAAS,YAAY,QAAiB,QAAmC;AACvE,QAAM,OAAO,CAAC,UAAkB;AAC9B,eAAW,MAAM,OAAO;AACtB,UAAI,WAAW,EAAE,KAAK,GAAG,SAAS,WAAW,GAAG,EAAG,IAAG,WAAW,OAAO,IAAI,GAAG,SAAS,MAAM,CAAC,CAAC,KAAK,GAAG;AACxG,UAAI,QAAQ,EAAE,EAAG,IAAG,OAAO,QAAQ,CAAC,MAAM,KAAK,EAAE,KAAK,CAAC;AAAA,IACzD;AAAA,EACF;AACA,SAAO,QAAQ,CAAC,MAAM,KAAK,EAAE,KAAK,CAAC;AACrC;AAWO,SAAS,gBAAgB,YAAoB,YAAsB,CAAC,GAAG,QAAkB,CAAC,GAAQ;AACvG,QAAM,OAAO,UAAU,IAAI,CAAC,QAAQ,aAAa,GAAG,CAAC;AACrD,QAAM,OAAO,iBAAiB,UAAU;AAGxC,QAAM,UAAU,CAAC,GAAG,KAAK,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,GAAI,KAAK,WAAW,CAAC,CAAE;AAC3E,QAAM,UAAU,KAAK,QAAQ,CAAC,MAAM,EAAE,OAAO;AAC7C,QAAM,SAAS,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;AACzD,UAAQ,QAAQ,CAAC,MAAM,YAAY,EAAE,QAAQ,MAAM,CAAC;AACpD,cAAY,KAAK,QAAQ,MAAM;AAG/B,QAAM,UAAU,KAAK,UAAU,CAAC,GAAG,IAAI,CAAC,MAAM;AAC5C,UAAM,IAAI,MAAM,EAAE,IAAI;AACtB,UAAM,IAAI,IAAI,EAAE,GAAG,GAAG,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,IAAI;AAGrD,WAAO,EAAE,SAAS,UAAU,CAAC,EAAE,SAAS,EAAE,GAAG,GAAG,QAAQ,EAAE,GAAG,IAAI;AAAA,EACnE,CAAC;AAED,SAAO;AAAA,IACL,OAAO,KAAK;AAAA,IACZ,QAAQ,KAAK;AAAA,IACb,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,IACzD;AAAA,IACA,GAAI,QAAQ,SAAS,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpC,QAAQ,KAAK;AAAA,IACb,GAAI,KAAK,aAAa,OAAO,KAAK,KAAK,SAAS,EAAE,SAAS,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;AAAA,IAC5F,GAAI,KAAK,SAAS,SAAS,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,IACxD,GAAI,KAAK,WAAW,SAAS,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;AAAA,IAC9D,UAAU,KAAK,YAAY,EAAE,KAAK,IAAI,gBAAgB,IAAI,QAAQ,CAAC,EAAE;AAAA,IACrE,GAAI,KAAK,cAAc,SAAS,EAAE,cAAc,KAAK,aAAa,IAAI,CAAC;AAAA,IACvE,GAAI,KAAK,aAAa,SAAS,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;AAAA,IACpE,GAAI,OAAO,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,EACpC;AACF;AAGO,IAAM,aAAa,CAAC,QAAqB,KAAK,UAAU,GAAG;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  compileFlatpack
3
- } from "./chunk-KXKX2RMX.js";
3
+ } from "./chunk-BPX2MOW2.js";
4
4
 
5
5
  // src/scopeProgram.ts
6
6
  function matchBrace(s, open) {
@@ -1181,4 +1181,4 @@ export {
1181
1181
  applyFixes,
1182
1182
  repairLoop
1183
1183
  };
1184
- //# sourceMappingURL=chunk-43MGBTUK.js.map
1184
+ //# sourceMappingURL=chunk-RQKXBHHM.js.map
package/dist/cli/flatc.js CHANGED
@@ -5,11 +5,11 @@ import {
5
5
  lintDocReport,
6
6
  programDiagnostics,
7
7
  repairLoop
8
- } from "../chunk-43MGBTUK.js";
8
+ } from "../chunk-RQKXBHHM.js";
9
9
  import {
10
10
  compileFlatpack,
11
11
  packToJSON
12
- } from "../chunk-KXKX2RMX.js";
12
+ } from "../chunk-BPX2MOW2.js";
13
13
 
14
14
  // src/cli/flatc.ts
15
15
  import { readFileSync, writeFileSync, existsSync, readdirSync, watch, mkdirSync, copyFileSync } from "fs";
@@ -79,9 +79,13 @@ Usage:
79
79
  --since <ver> what the given files (.flatink / .flat, as many as you like) draw DIFFERENTLY from
80
80
  that version \u2014 a one-shot report for an upgrade, with file:line. Recorded so far:
81
81
  before 0.36 a path made only of lines was rounded at its gentle turns. Lists the
82
- paths that move by 1% of their size or more; add --all for the slighter ones
82
+ paths that move by 1% of their size or more, and the open lines that move by 2
83
+ units or more; add --all for the slighter ones
83
84
  --seed N (with --play) seed of random(). A replay is always seeded (default 1): the same script
84
85
  gives the same result twice; another N gives another draw
86
+ --settle N (with --play, --render --script) simulation steps after each pointer event (default 1:
87
+ a press, a move, a release each take a frame, so "every frame" sees the drag;
88
+ 0 = the instantaneous replay). "settle": N on a gesture sets it for that one
85
89
  --trace (with --play) HUMAN-READABLE log per gesture: emitted sends + variable diff (debug)
86
90
  --script <f> JSON gesture script: [{ "type": "down|move|up|cancel", "x", "y" }, { "type": "set", "name", "value" }, { "type": "wait", "frames": N }, { "type": "wheel", "dy": N }]
87
91
  semantic (by NAME, the engine resolves coords): { "type": "drag", "source", "target" } \xB7 { "type": "tap", "target" } (or "x", "y": a point)
@@ -90,7 +94,9 @@ Usage:
90
94
  "wait" lets the simulation run N fixed steps (60 Hz): "every frame" + playhead advance like in real playback
91
95
  "expect" self-verifies: { "type": "expect", "sends": ["done"], "vars": { "score": 3 } } \u2192 exits \u22600 on mismatch
92
96
  (sends = sequence of names emitted SINCE the last expect; vars = current state). Great in CI.
93
- --render render a PNG IMAGE (headless skia): see what we draw (positioning)
97
+ --render render a PNG IMAGE (headless skia): see what we draw (positioning). With --script,
98
+ replays the gestures first and renders the state they reach; a { "type": "shot",
99
+ "name": "x" } gesture writes <out>.x.png at that point; a failed "expect" exits \u22600
94
100
  --preview wrap ONE symbol of a <library.flat> into a playable Doc (a single centered instance on an
95
101
  auto-sized stage) \u2192 a .flatpack to drop in the browser player, or a PNG with --render.
96
102
  No wrapper .flatink to author by hand. Output defaults to <library>.<symbol>.flatpack/.png
@@ -294,7 +300,7 @@ function loadDoc(filePath, noLibs = false, explicitFlats = []) {
294
300
  if (filePath.endsWith(".flatink")) return buildDocFromProgram(filePath, explicitFlats, "inline", "", noLibs).doc;
295
301
  return sanitizeDoc(JSON.parse(readFileSync(filePath, "utf8")));
296
302
  }
297
- function playOnce(filePath, scriptPath, trace, noLibs = false, explicitFlats = [], seed) {
303
+ function playOnce(filePath, scriptPath, trace, noLibs = false, explicitFlats = [], seed, settle) {
298
304
  if (!scriptPath) {
299
305
  process.stderr.write("flatc: --play requires --script <gestures.json>\n");
300
306
  return 1;
@@ -323,7 +329,9 @@ function playOnce(filePath, scriptPath, trace, noLibs = false, explicitFlats = [
323
329
  process.stderr.write("flatc: the script must be an array of gestures\n");
324
330
  return 1;
325
331
  }
326
- const res = playHeadless(doc, gestures, { trace, seed });
332
+ const res = playHeadless(doc, gestures, { trace, seed, settle });
333
+ for (const w of res.warnings ?? []) process.stderr.write(`flatc: warning: ${w}
334
+ `);
327
335
  if (!trace) process.stdout.write(JSON.stringify(res, null, 2) + "\n");
328
336
  else
329
337
  for (const s of res.steps ?? []) {
@@ -369,7 +377,7 @@ async function renderDocToFile(doc, outPath, frame, vars, scale, steps, params =
369
377
  `);
370
378
  return 0;
371
379
  }
372
- async function renderOnce(filePath, out, frame, vars, scale, steps, scaleAuto, noLibs = false, params = {}, explicitFlats = []) {
380
+ async function renderOnce(filePath, out, frame, vars, scale, steps, scaleAuto, noLibs = false, params = {}, explicitFlats = [], scriptPath = "", settle) {
373
381
  let doc;
374
382
  try {
375
383
  doc = loadDoc(filePath, noLibs, explicitFlats);
@@ -382,7 +390,66 @@ async function renderOnce(filePath, out, frame, vars, scale, steps, scaleAuto, n
382
390
  if (dur > 0 && frame >= dur) process.stderr.write(`flatc: --frame ${frame} is past the timeline (durationFrames ${dur}); the playhead WRAPS, so this renders frame ${frame % dur}
383
391
  `);
384
392
  const outPath = out ? resolve(out) : join(dirname(filePath), basename(filePath, extname(filePath)) + ".png");
385
- return renderDocToFile(doc, outPath, frame, vars, scaleAuto ? autoScale(doc.width, doc.height) : scale, steps, params);
393
+ const sc = scaleAuto ? autoScale(doc.width, doc.height) : scale;
394
+ if (!scriptPath) return renderDocToFile(doc, outPath, frame, vars, sc, steps, params);
395
+ return renderAfterScript(doc, outPath, scriptPath, frame, vars, sc, steps, params, settle);
396
+ }
397
+ async function renderAfterScript(doc, outPath, scriptPath, frame, vars, scale, steps, params, settle) {
398
+ if (!existsSync(scriptPath)) {
399
+ process.stderr.write(`flatc: script not found: ${scriptPath}
400
+ `);
401
+ return 1;
402
+ }
403
+ let gestures;
404
+ try {
405
+ gestures = JSON.parse(readFileSync(scriptPath, "utf8"));
406
+ } catch (e) {
407
+ process.stderr.write(`flatc: invalid JSON script: ${e.message}
408
+ `);
409
+ return 1;
410
+ }
411
+ if (!Array.isArray(gestures)) {
412
+ process.stderr.write("flatc: the script must be an array of gestures\n");
413
+ return 1;
414
+ }
415
+ const shotPath = (name) => join(dirname(outPath), `${basename(outPath, extname(outPath))}.${name.replace(/[^\w.-]/g, "_")}${extname(outPath) || ".png"}`);
416
+ const written = [];
417
+ let failures = [], warned = [];
418
+ try {
419
+ const { createRenderer } = await import("./render.js");
420
+ const r = await createRenderer(doc, { scale, params: Object.keys(params).length ? params : void 0, interactive: true, settle });
421
+ try {
422
+ await r.frame(frame, { vars: Object.keys(vars).length ? vars : void 0 });
423
+ let shots = 0;
424
+ for (const g of gestures) {
425
+ if (g.type === "shot") {
426
+ const p = shotPath(g.name || String(++shots));
427
+ writeFileSync(p, await r.capture());
428
+ written.push(basename(p));
429
+ continue;
430
+ }
431
+ const rep = r.play([g]);
432
+ failures = rep.expectFailures;
433
+ warned = rep.warnings;
434
+ }
435
+ if (steps > 0) r.play([{ type: "wait", frames: Math.floor(steps) }]);
436
+ writeFileSync(outPath, await r.capture());
437
+ } finally {
438
+ r.close();
439
+ }
440
+ } catch (e) {
441
+ process.stderr.write(`flatc: render failed: ${e.message}
442
+ `);
443
+ return 1;
444
+ }
445
+ const played = gestures.filter((g) => g.type !== "shot").length;
446
+ process.stdout.write(`flatc: ${basename(outPath)} \u2713 ${doc.width}\xD7${doc.height} \xD7${scale} \xB7 after ${played} gesture(s)${steps ? ` \xB7 ${steps} step(s)` : ""}${written.length ? ` \xB7 shots: ${written.join(", ")}` : ""}
447
+ `);
448
+ for (const w of warned) process.stderr.write(`flatc: warning: ${w}
449
+ `);
450
+ for (const f of failures) process.stderr.write(`flatc: \u2717 ${f}
451
+ `);
452
+ return failures.length ? 1 : 0;
386
453
  }
387
454
  var gcd = (a, b) => {
388
455
  a = Math.abs(a);
@@ -442,6 +509,15 @@ function buildPreviewDoc(flatPath, symbolName, pad, bboxMode, setSpec = {}) {
442
509
  const dur = previewDuration(symbol, symbols);
443
510
  const timeline = { fps: tl?.fps ?? 24, durationFrames: dur, tracks: [] };
444
511
  let doc = { width: 1, height: 1, layers: [layer], symbols, timeline };
512
+ const entries = Object.entries(setSpec);
513
+ if (entries.length) {
514
+ const known = /* @__PURE__ */ new Set([...(symbol.states ?? []).map((s) => s.param), ...(symbol.params ?? []).map((p) => p.name)]);
515
+ for (const [k, v] of entries) {
516
+ if (!known.has(k)) process.stderr.write(`flatc: --set ${k}: "${symbol.name}" exposes no such param (${[...known].join(", ") || "none"})
517
+ `);
518
+ else (instance.params ??= {})[k] = v;
519
+ }
520
+ }
445
521
  const sampleFrames = (n) => {
446
522
  const step = Math.max(1, Math.ceil(n / 240));
447
523
  const fs = [];
@@ -449,7 +525,7 @@ function buildPreviewDoc(flatPath, symbolName, pad, bboxMode, setSpec = {}) {
449
525
  if (fs[fs.length - 1] !== n - 1) fs.push(n - 1);
450
526
  return fs;
451
527
  };
452
- const bb = bboxMode === "all" ? containerBBoxUnion(doc, instance, sampleFrames(dur)) : containerBBox(doc, instance, 0);
528
+ const bb = bboxMode === "all" ? containerBBoxUnion(doc, instance, sampleFrames(dur), instance.transform, true) : containerBBox(doc, instance, 0, instance.transform, false, true);
453
529
  if (bb) {
454
530
  doc = { ...doc, width: Math.max(1, Math.ceil(bb.maxX - bb.minX) + pad * 2), height: Math.max(1, Math.ceil(bb.maxY - bb.minY) + pad * 2) };
455
531
  instance.transform = { ...IDENTITY, e: pad - bb.minX, f: pad - bb.minY };
@@ -457,15 +533,6 @@ function buildPreviewDoc(flatPath, symbolName, pad, bboxMode, setSpec = {}) {
457
533
  doc = { ...doc, width: 512, height: 512 };
458
534
  instance.transform = { ...IDENTITY, e: 256, f: 256 };
459
535
  }
460
- const entries = Object.entries(setSpec);
461
- if (entries.length) {
462
- const known = /* @__PURE__ */ new Set([...(symbol.states ?? []).map((s) => s.param), ...(symbol.params ?? []).map((p) => p.name)]);
463
- for (const [k, v] of entries) {
464
- if (!known.has(k)) process.stderr.write(`flatc: --set ${k}: "${symbol.name}" exposes no such param (${[...known].join(", ") || "none"})
465
- `);
466
- else (instance.params ??= {})[k] = v;
467
- }
468
- }
469
536
  return { doc, symbol, others: symbols.filter((s) => s !== symbol).map((s) => s.name) };
470
537
  }
471
538
  async function previewOnce(flatPath, symbolName, out, frame, vars, scale, steps, doRender, pad, bboxMode, setSpec, scaleAuto) {
@@ -478,6 +545,11 @@ async function previewOnce(flatPath, symbolName, out, frame, vars, scale, steps,
478
545
  return 1;
479
546
  }
480
547
  const { doc, symbol, others } = built;
548
+ if (frame > 0 && symbol.states?.length) {
549
+ const sm = symbol.states[0];
550
+ process.stderr.write(`flatc: --frame ${frame} does not move "${symbol.name}": its pose is set by its states (\`${sm.param}\`), not by the playhead. To see a state, or a point between two: --set ${sm.param}=<${sm.states.map((x) => x.name).join(" | ")} | a value, e.g. ${sm.param}=0.5>
551
+ `);
552
+ }
481
553
  const stem = `${basename(flatPath, extname(flatPath))}.${symbol.name}`;
482
554
  let code;
483
555
  if (doRender) {
@@ -494,6 +566,7 @@ async function previewOnce(flatPath, symbolName, out, frame, vars, scale, steps,
494
566
  `);
495
567
  return code;
496
568
  }
569
+ var SINCE_OPEN_UNITS = 2;
497
570
  function sinceReport(files, since, all = false) {
498
571
  const [maj, min] = since.split(".").map(Number);
499
572
  if (!Number.isFinite(maj) || !Number.isFinite(min)) {
@@ -523,7 +596,7 @@ function sinceReport(files, since, all = false) {
523
596
  const xs = sub.segments.map((sg) => sg.anchor.x), ys = sub.segments.map((sg) => sg.anchor.y);
524
597
  const size = Math.hypot(Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys));
525
598
  if (moved <= 0 || size <= 0) continue;
526
- if (moved < size * 0.01 && !all) {
599
+ if (moved < size * 0.01 && (sub.closed || moved < SINCE_OPEN_UNITS) && !all) {
527
600
  slight++;
528
601
  continue;
529
602
  }
@@ -536,7 +609,7 @@ function sinceReport(files, since, all = false) {
536
609
  }
537
610
  }
538
611
  const n = open + closed;
539
- const rest = slight ? ` ${slight} more move by less than 1% of their size (\`--all\` lists them).` : "";
612
+ const rest = slight ? ` ${slight} more move by less than 1% of their size, and by less than ${SINCE_OPEN_UNITS} units if open (\`--all\` lists them).` : "";
540
613
  process.stdout.write(n ? `flatc: ${n} path(s) of lines drawn differently since ${since}: ${open} open, ${closed} closed.${rest} Nothing was modified. (Rounded rectangles and paths mixing lines with curves changed too \u2014 their straight sides no longer bulge \u2014 and are not listed.)
541
614
  ` : `flatc: no path of lines is drawn visibly differently since ${since} in ${files.length} file(s).${rest}
542
615
  `);
@@ -549,6 +622,7 @@ function run(argv) {
549
622
  let frame = 0, scale = 2, steps = 0, pad = 24;
550
623
  let scaleAuto = false;
551
624
  let seed;
625
+ let settle;
552
626
  let since = "";
553
627
  let bboxMode = "all";
554
628
  let assetMode = "inline";
@@ -579,6 +653,9 @@ function run(argv) {
579
653
  } else if (a === "--seed") {
580
654
  const v = Number(args[++i]);
581
655
  if (Number.isFinite(v)) seed = v;
656
+ } else if (a === "--settle") {
657
+ const v = Number(args[++i]);
658
+ if (Number.isFinite(v) && v >= 0) settle = Math.floor(v);
582
659
  } else if (a === "--steps") steps = Math.max(0, Number(args[++i] ?? "0") || 0);
583
660
  else if (a === "--scale") {
584
661
  const v = args[++i] ?? "2";
@@ -607,8 +684,8 @@ function run(argv) {
607
684
  if (since) return sinceReport(positional.map((f) => resolve(f)), since, args.includes("--all"));
608
685
  const explicitFlats = positional.slice(1);
609
686
  if (doPreview) return previewOnce(filePath, symbolName, out, frame, vars, scale, steps, doRender, pad, bboxMode, setSpec, scaleAuto);
610
- if (doRender) return renderOnce(filePath, out, frame, vars, scale, steps, scaleAuto, noLibs, setSpec, explicitFlats);
611
- if (doPlay) return playOnce(filePath, scriptPath, doTrace, noLibs, explicitFlats, seed);
687
+ if (doRender) return renderOnce(filePath, out, frame, vars, scale, steps, scaleAuto, noLibs, setSpec, explicitFlats, scriptPath, settle);
688
+ if (doPlay) return playOnce(filePath, scriptPath, doTrace, noLibs, explicitFlats, seed, settle);
612
689
  const action = checkOnly && filePath.endsWith(".flat") ? () => checkFlatLibs([filePath, ...explicitFlats]) : () => compileOnce(filePath, explicitFlats, out, checkOnly, assetMode, noLibs, doFix);
613
690
  if (doWatch) {
614
691
  const code = action();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/flatc.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// flatc — the \"modern SWF\" command-line compiler.\n//\n// The VSCode-first flow: you write a `.flatink` program (composition + logic, DSL)\n// + `.flat` asset libs (visuals, exported by the editor) + media, then:\n//\n// flatc game.flatink hero.flat decor.flat -o game.flatpack\n//\n// → a single `.flatpack` (the baked Doc as JSON) that the player runs. Media referenced\n// by `asset \"id\" \"path\" kind` are EMBEDDED (paths relative to the program).\n// ─────────────────────────────────────────────────────────────────────────────\nimport { readFileSync, writeFileSync, existsSync, readdirSync, watch, mkdirSync, copyFileSync } from 'node:fs'\nimport { resolve, dirname, basename, extname, join, relative, isAbsolute } from 'node:path'\nimport { compileFlatpack, packToJSON, type MediaMap } from '../compile'\nimport { parseProgramFull, parseFlatLib } from '@flatkit/engine/flatFormat'\nimport { hasPackage } from '@flatkit/engine/stdlib'\nimport { parseUnits } from '@flatkit/engine/dsl'\nimport { sanitizeDoc } from '@flatkit/engine/validateDoc'\nimport { unitsToFunctions } from '@flatkit/engine/scriptDoc'\nimport { containerBBox, containerBBoxUnion } from '@flatkit/engine/groups'\nimport { isInstance, isGroup } from '@flatkit/engine/layers'\nimport { IDENTITY } from '@flatkit/engine/transform'\nimport { parsePathData } from '@flatkit/engine/svgPath'\nimport { softVertexCount, smoothingDeviation } from '@flatkit/engine/path'\nimport { lintDocReport, docHasErrors } from '../programDoc'\nimport { applyFixes, formatDiagnostics, programDiagnostics, repairLoop } from '../check'\nimport { FlatSyntaxError } from '@flatkit/engine/flatFormat'\nimport { playHeadless, type Gesture } from '@flatkit/player/debug'\nimport type { FuncDef } from '@flatkit/engine/actions'\nimport type { Doc, Instance, Item, Layer, SymbolDef } from '@flatkit/types'\n\nconst MIME: Record<string, string> = {\n '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif',\n '.webp': 'image/webp', '.svg': 'image/svg+xml', '.avif': 'image/avif',\n '.mp3': 'audio/mpeg', '.wav': 'audio/wav', '.ogg': 'audio/ogg', '.m4a': 'audio/mp4',\n // Fonts (RFC 8081 media types) — matches the editor's import (`font/woff2` default, FontFace API).\n '.woff2': 'font/woff2', '.woff': 'font/woff', '.ttf': 'font/ttf', '.otf': 'font/otf',\n // NB: video (.mp4/.webm/…) intentionally omitted — no video runtime in the editor or player yet.\n}\nconst mimeFor = (path: string): string => MIME[extname(path).toLowerCase()] ?? 'application/octet-stream'\n\n/**\n * True if `target` resolves to a file inside `baseDir` (or baseDir itself). Guards against a hostile\n * program escaping its folder via `../` in a media path or a `use` package name — compiling an untrusted\n * `.flatink` must never read (and embed) arbitrary host files.\n */\nfunction isWithin(baseDir: string, target: string): boolean {\n const rel = relative(baseDir, target)\n return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel))\n}\n\nfunction printHelp(): void {\n process.stdout.write(`flatc — compile a FlatInk program into a .flatpack\n\nUsage:\n flatc <program.flatink> [assets.flat …] [-o output.flatpack]\n flatc <program.flatink> --watch\n flatc --play <program.flatink | scene.flatpack> --script <gestures.json>\n flatc --render <program.flatink | scene.flatpack> -o out.png [--frame N] [--at k=v[,k2=v2]] [--scale S]\n flatc --preview <library.flat> [--symbol NAME] [-o out.flatpack | --render -o out.png]\n flatc <library.flat> [more.flat …] --check\n\n program.flatink the program (composition + logic, text DSL)\n assets.flat visual asset libs (default: every .flat in the program's folder)\n -o, --out output file (default: <program>.flatpack — the CANONICAL name, JSON inside)\n --assets MODE media baking: 'inline' (default, base64 in the .flatpack) or 'external'\n (sidecar <out>.assets/ folder; asset.data = relative key — serve the folder and\n play with sameOriginAssetResolver(<flatpackUrl>))\n --check semantic lint only (no .flatpack); exits ≠0 on ERROR (warnings do not stop). Lints a\n program .flatink OR an asset library .flat (per-symbol; several .flat are merged)\n --fix apply the MECHANICAL repairs the diagnostics carry (missing separator, run-on line)\n and re-check, iterating. Writes the file AT MOST ONCE, and never a text it has not\n re-checked; a file it cannot repair is left untouched, mtime included. Implies --check.\n --no-libs do NOT auto-discover the .flat files sitting next to the program (use it in a working\n folder, where a neighbouring scratch file is not a dependency)\n --watch recompile on every change in the folder (agent → player loop)\n --play run the file WITHOUT a canvas, replay --script and print { sends, vars } (JSON)\n --since <ver> what the given files (.flatink / .flat, as many as you like) draw DIFFERENTLY from\n that version — a one-shot report for an upgrade, with file:line. Recorded so far:\n before 0.36 a path made only of lines was rounded at its gentle turns. Lists the\n paths that move by 1% of their size or more; add --all for the slighter ones\n --seed N (with --play) seed of random(). A replay is always seeded (default 1): the same script\n gives the same result twice; another N gives another draw\n --trace (with --play) HUMAN-READABLE log per gesture: emitted sends + variable diff (debug)\n --script <f> JSON gesture script: [{ \"type\": \"down|move|up|cancel\", \"x\", \"y\" }, { \"type\": \"set\", \"name\", \"value\" }, { \"type\": \"wait\", \"frames\": N }, { \"type\": \"wheel\", \"dy\": N }]\n semantic (by NAME, the engine resolves coords): { \"type\": \"drag\", \"source\", \"target\" } · { \"type\": \"tap\", \"target\" } (or \"x\", \"y\": a point)\n · { \"type\": \"scratch\", \"target\" } (sweeps a reveal zone) · { \"type\": \"connect\", \"source\", \"target\" } (pulls a link wire)\n { \"type\": \"key\", \"name\": \"ArrowRight\", \"frames\": N } holds a key (keys.<name> = 1) N steps, then releases it\n \"wait\" lets the simulation run N fixed steps (60 Hz): \"every frame\" + playhead advance like in real playback\n \"expect\" self-verifies: { \"type\": \"expect\", \"sends\": [\"done\"], \"vars\": { \"score\": 3 } } → exits ≠0 on mismatch\n (sends = sequence of names emitted SINCE the last expect; vars = current state). Great in CI.\n --render render a PNG IMAGE (headless skia): see what we draw (positioning)\n --preview wrap ONE symbol of a <library.flat> into a playable Doc (a single centered instance on an\n auto-sized stage) → a .flatpack to drop in the browser player, or a PNG with --render.\n No wrapper .flatink to author by hand. Output defaults to <library>.<symbol>.flatpack/.png\n --symbol NAME (with --preview) which symbol to wrap (default: the first in the lib)\n --bbox all|frame0 (with --preview) auto-size to the UNION over all frames (default 'all', no clipping of\n drifting/rotating/growing motion) or just frame 0 ('frame0', the old behavior)\n --pad N (with --preview) padding in px around the symbol's bounds (default 24)\n --set p=v[,p2=v2] set a SYMBOL's exposed params (works with --preview AND --render); a state param\n takes a state NAME\n or a number (e.g. --set door=open), others take a number → baked into the preview\n --frame N (with --render) target frame (default 0)\n --at k=v[,k2=v2] (with --render) force variables → capture a given state (e.g. a step of an escape)\n --steps N (with --render) run N fixed sim steps (60 Hz, every-frame) BEFORE capture → see a\n stateful act unfold without forcing every derived variable by hand in --at\n --scale S|auto (with --render) resolution factor (default 2); 'auto' picks one from the canvas size\n (enlarges small/thin assets so fine filaments stay legible, large assets stay 1x)\n -h, --help this help\n\nMedia referenced by 'asset \"id\" \"path\" kind' are embedded (paths relative to the program).\n`)\n}\n\n/** How media is baked: `inline` = base64 data-URI in the .flatpack; `external` = relative key + sidecar files. */\ntype AssetMode = 'inline' | 'external'\ntype MediaCopy = { src: string; key: string } // external mode: source file → relative key (forward slashes)\ntype BuildResult = { doc: Doc; flatLibs: number; packages: number; media: number; mediaCopies: MediaCopy[]; src: string }\n\n/**\n * Reads a `.flatink`, resolves libs/packages/media, compiles → standalone Doc. Throws on compile error.\n * `assetMode`: `inline` embeds each media as a base64 data-URI (default); `external` keeps `asset.data` as a\n * relative key (`<assetsDir>/<path>`) and returns the files to copy beside the .flatpack (no base64 bloat).\n */\nfunction buildDocFromProgram(programPath: string, explicitFlats: string[] = [], assetMode: AssetMode = 'inline', assetsDir = '', noLibs = false, srcOverride?: string): BuildResult {\n const baseDir = dirname(programPath)\n // `srcOverride` compiles a CANDIDATE text as if it were the file, without writing it. `--fix` needs it:\n // it must re-check each repair before committing anything, and writing-then-reverting leaves a window\n // where the author's file holds a version we already know we do not want.\n const programSrc = srcOverride ?? readFileSync(programPath, 'utf8')\n const prog = parseProgramFull(programSrc)\n\n // .flat libs: explicit if provided, otherwise auto-discover the .flat files in the program's folder\n // (`--no-libs` opts out — in a working folder, a neighbouring scratch file is not a dependency).\n const flatPaths = new Set<string>(explicitFlats.map((p) => resolve(p)))\n if (!flatPaths.size && !noLibs) for (const f of readdirSync(baseDir).filter((f) => f.endsWith('.flat'))) flatPaths.add(join(baseDir, f))\n\n // PACKAGES: non-stdlib `use \"x\"` → local files inlined (x.flatink = functions, x.flat = symbols).\n const pkgFunctions: FuncDef[] = []\n const localResolved = new Set<string>()\n for (const name of prog.imports ?? []) {\n if (hasPackage(name)) continue // stdlib → left as a reference\n const fink = join(baseDir, name + '.flatink')\n const fflat = join(baseDir, name + '.flat')\n if (!isWithin(baseDir, fink) || !isWithin(baseDir, fflat)) { process.stderr.write(`flatc: package outside the program folder (ignored): \"${name}\"\\n`); continue }\n let found = false\n if (existsSync(fink)) { for (const f of unitsToFunctions(parseUnits(readFileSync(fink, 'utf8')).units)) pkgFunctions.push(f, { ...f, name: `${name}.${f.name}` }); found = true }\n if (existsSync(fflat)) { flatPaths.add(fflat); found = true }\n if (found) localResolved.add(name)\n else process.stderr.write(`flatc: package not found: \"${name}\" (neither stdlib, nor ${name}.flatink / ${name}.flat)\\n`)\n }\n // Parse each lib HERE so a failure names the FILE. Libs are auto-discovered from the folder, so the one\n // that fails is often a neighbour the author never mentioned — a bare \"compile error: …\" sent them hunting\n // through their own program for a fault that was never in it.\n const assetSrcs = [...flatPaths].map((p) => {\n const src = readFileSync(p, 'utf8')\n try { parseFlatLib(src) } catch (e) { throw new Error(`${basename(p)}: ${(e as Error).message} (pass --no-libs to ignore the .flat files sitting next to the program)`, { cause: e }) }\n if (programConstructsIn(src).length) process.stderr.write(`flatc: ${basename(p)}: looks like a PROGRAM saved as .flat — loaded as a symbol library, so its scene/object blocks are ignored. Rename it to .flatink, or pass --no-libs.\\n`)\n return src\n })\n\n // Media: each `asset … \"path\" …` resolved relative to the program. `inline` → base64 data-URI baked in;\n // `external` → `asset.data` becomes a relative key and the file is copied next to the .flatpack.\n const media: MediaMap = {}\n const mediaCopies: MediaCopy[] = []\n for (const a of prog.assets ?? []) {\n const mp = resolve(baseDir, a.data)\n if (!isWithin(baseDir, mp)) { process.stderr.write(`flatc: media outside the program folder (ignored): ${a.data}\\n`); continue }\n if (!existsSync(mp)) { process.stderr.write(`flatc: missing media (ignored): ${a.data}\\n`); continue }\n const mime = mimeFor(mp)\n if (assetMode === 'external') {\n const key = `${assetsDir}/${a.data.replace(/\\\\/g, '/')}` // URL-form relative key (resolved by the host)\n media[a.data] = { mime, data: key }\n mediaCopies.push({ src: mp, key })\n } else {\n media[a.data] = { mime, data: `data:${mime};base64,${readFileSync(mp).toString('base64')}` }\n }\n }\n\n let doc = compileFlatpack(programSrc, assetSrcs, media)\n if (pkgFunctions.length) doc = { ...doc, functions: [...(doc.functions ?? []), ...pkgFunctions] }\n const stdImports = (doc.imports ?? []).filter(hasPackage)\n doc = { ...doc, imports: stdImports.length ? stdImports : undefined }\n // The source travels with the Doc: the source-level passes (`programDiagnostics`) need the author's text,\n // since a parse-level drop and an `object` block binding to nothing leave no trace in the compiled Doc.\n return { doc, flatLibs: flatPaths.size, packages: localResolved.size, media: Object.keys(media).length, mediaCopies, src: programSrc }\n}\n\n/** Compile once (write or --check). Returns the exit code. */\nfunction compileOnce(programPath: string, explicitFlats: string[], out: string, checkOnly: boolean, assetMode: AssetMode = 'inline', noLibs = false, doFix = false): number {\n const outPath = out ? resolve(out) : join(dirname(programPath), basename(programPath, extname(programPath)) + '.flatpack')\n // External mode: sidecar folder next to the .flatpack, e.g. `game.flatpack` → `game.assets/`.\n const assetsDir = assetMode === 'external' ? basename(outPath, extname(outPath)) + '.assets' : ''\n const build = (src?: string) => buildDocFromProgram(programPath, explicitFlats, assetMode, assetsDir, noLibs, src)\n // `--fix` on a source the parser REJECTS. That case never reaches the diagnostics pass below, and it is\n // exactly where a mechanical repair earns its keep: there is no partial result to work from. Repaired\n // IN MEMORY, one syntax error per turn, so the whole of `--fix` still writes the file at most once.\n let parseRepaired = ''\n if (doFix) {\n let candidate = readFileSync(programPath, 'utf8')\n for (let pass = 0; pass < 10; pass++) {\n try { build(candidate); break } catch (e) {\n if (!(e instanceof FlatSyntaxError) || !e.fix) break\n const { text, applied } = applyFixes(candidate, [{ scope: 'scene', line: e.line, col: e.col, severity: 'error', message: e.message, fix: e.fix }])\n if (!applied) break\n candidate = text\n parseRepaired = candidate\n process.stderr.write(`flatc: --fix: repaired \\`${e.message.split(':')[0]}\\` in ${basename(programPath)}\\n`)\n }\n }\n }\n let built: BuildResult\n try { built = build(parseRepaired || undefined) }\n catch (e) {\n process.stderr.write(`flatc: compile error: ${(e as Error).message}\\n`)\n return 1\n }\n const { doc } = built\n // The SAME pass the `checkProgram` API runs — source-level diagnostics (statements the parser dropped,\n // `object` blocks binding to nothing) merged with the semantic lint of the Doc. Shared on purpose: a\n // check that lives only in the CLI is a check an integrator has to shell out for.\n const diagnostics = programDiagnostics(doc, built.src)\n const report = formatDiagnostics(diagnostics)\n const hasErrors = diagnostics.some((d) => d.severity === 'error')\n // `--fix` applies the MECHANICAL repairs the diagnostics carry (a missing separator, a run-on line),\n // then re-checks. Two rules make it safe to run unattended:\n // • it ITERATES, because repairing one error unmasks the next — a run-on interactor line swallows the\n // statements under it, and their own diagnostics only appear once it is split;\n // • it writes only if the error count strictly DROPS at each pass. An auto-fix that makes things worse\n // must not survive, and the honest way to know is to run the same check again on the result.\n if (doFix) {\n // START from the text the diagnostics were computed on. Falling back to the file would apply edits\n // whose line/col were measured against the parse-repaired text -- a comma insertion survives that,\n // a multi-line replacement does not.\n const original = parseRepaired || readFileSync(programPath, 'utf8')\n const { text, applied: total, first, errors: before } = repairLoop(original, diagnostics, (src) =>\n programDiagnostics(buildDocFromProgram(programPath, explicitFlats, assetMode, assetsDir, noLibs, src).doc, src))\n if (!total && !parseRepaired) {\n // NOT even a rewrite of the identical bytes: a new mtime wakes every watcher pointed at the folder,\n // and a write fails outright on a read-only checkout we had no reason to touch.\n process.stderr.write(`flatc: --fix: nothing to repair mechanically${hasErrors ? ' — the remaining errors need a decision, not a separator\\n' : '\\n'}`)\n } else {\n writeFileSync(programPath, text) // ONE write, of a text this loop has already re-checked\n process.stderr.write(`flatc: --fix: ${total} repair(s) applied to ${basename(programPath)} · errors ${first} → ${before}\\n`)\n return compileOnce(programPath, explicitFlats, out, checkOnly, assetMode, noLibs, false)\n }\n }\n if (checkOnly) {\n if (report) process.stderr.write(report + '\\n')\n if (hasErrors) return 1\n // Success line deliberately avoids the word \"error\" (the report on a FAILURE already prints \"error\" to\n // stderr + exits ≠0) so a `grep error` over the output can't false-positive — the real signal is the exit\n // code; on success, only warnings remain (non-blocking), surfaced as a count.\n const warnings = diagnostics.length\n process.stdout.write(`flatc: check passed ✓${warnings ? ` · ${warnings} warning(s)` : ''}\\n`)\n return 0\n }\n if (report) process.stderr.write(report + '\\n') // compile anyway: diagnostics as warnings\n writeFileSync(outPath, packToJSON(doc))\n const outDir = dirname(outPath)\n for (const c of built.mediaCopies) {\n const dest = join(outDir, ...c.key.split('/'))\n if (!isWithin(outDir, dest)) continue // defensive: never write outside the output folder\n mkdirSync(dirname(dest), { recursive: true })\n copyFileSync(c.src, dest)\n }\n const where = assetMode === 'external' ? ` (external → ${assetsDir}/)` : ''\n process.stdout.write(`flatc: ${basename(outPath)} ✓ ${doc.symbols.length} symbol(s) · ${built.flatLibs} lib(s) · ${built.packages} package(s) · ${built.media} media${where}\\n`)\n return 0\n}\n\n/** PROGRAM-only constructs found in what is supposed to be a `.flat` symbol library. `parseFlatLib` reads a\n * `.flat` as a bag of symbols and silently ignores everything else, so a whole program saved under the wrong\n * extension parsed to \"0 symbol(s)\" and passed `--check` with nothing verified — the worst possible answer\n * for tooling, since a check written against the wrong extension ALWAYS passes and looks like a safety net.\n * Both constructs are anchored at line start: only a real block matches, not a mention inside a string. */\nfunction programConstructsIn(src: string): string[] {\n const found: string[] = []\n if (/^[ \\t]*scene\\s*\\{/m.test(src)) found.push('a `scene { … }` block')\n if (/^[ \\t]*object\\s+\"/m.test(src)) found.push('`object \"…\" { … }` blocks')\n return found\n}\n\n/**\n * `--check` for a `.flat` asset LIBRARY (one or several): parse via `parseFlatLib` (NOT the program parser,\n * which would choke on `symbol`/`params`/`layer`), merge the symbols into an empty-scene Doc, and run the SAME\n * Doc lint as a program -> per-symbol diagnostics (`[<Symbol>] line:col: …`). Reuses `lintDocReport`/\n * `docHasErrors`, so every existing check (params-in-expr, undeclared color param in a paint, unknown\n * functions/objects…) applies for free, with the same `[scope] line:col: level: msg` format and exit code.\n * No `behaviorDiagnostics` (those are scene-level — a pure lib has no scene).\n */\nfunction checkFlatLibs(flatPaths: string[]): number {\n const symbols: SymbolDef[] = []\n for (const p of flatPaths) {\n let src: string\n try { src = readFileSync(p, 'utf8') } catch (e) { process.stderr.write(`flatc: cannot read: ${(e as Error).message}\\n`); return 1 }\n const disguised = programConstructsIn(src)\n if (disguised.length) { // a PROGRAM saved as `.flat` — see programConstructsIn\n process.stderr.write(`flatc: ${basename(p)}: this is a PROGRAM, not a symbol library — it contains ${disguised.join(' and ')}, which a .flat never carries and which --check therefore does NOT verify. Rename it to .flatink.\\n`)\n return 1\n }\n try { symbols.push(...parseFlatLib(src).symbols) } // ids are uid-unique across calls -> safe to merge libs\n catch (e) { process.stderr.write(`flatc: ${basename(p)}: ${(e as Error).message}\\n`); return 1 } // a malformed lib -> a clean parse error (not \"[scene] …\")\n }\n const doc: Doc = { width: 1, height: 1, timeline: { fps: 24, durationFrames: 1, tracks: [] }, variables: {}, layers: [], symbols }\n const report = lintDocReport(doc) // scene scope is empty -> only per-symbol diagnostics\n if (report) process.stderr.write(report + '\\n')\n if (docHasErrors(doc)) return 1\n // \"check passed\", NOT \"no errors\" — the word \"error\" must not appear on success (a `grep error` trap); the\n // exit code is the signal, and any remaining lines are warnings (non-blocking), reported as a count.\n const warnings = report ? report.split('\\n').filter(Boolean).length : 0\n process.stdout.write(`flatc: check passed ✓ ${symbols.length} symbol(s)${warnings ? ` · ${warnings} warning(s)` : ''}\\n`)\n return 0\n}\n\n/** Loads a Doc from a `.flatink` (compiled) or a `.flatpack`/`.flatpack.json` (already-baked JSON). */\nfunction loadDoc(filePath: string, noLibs = false, explicitFlats: string[] = []): Doc {\n // The `.flat` libraries named on the command line are part of the program, exactly as for a compile:\n // dropping them here left every instance of theirs unresolved — an empty scene, with `--check` green.\n if (filePath.endsWith('.flatink')) return buildDocFromProgram(filePath, explicitFlats, 'inline', '', noLibs).doc\n // .flatpack (canonical) or .flatpack.json (alias): untrusted JSON → normalize before use.\n return sanitizeDoc(JSON.parse(readFileSync(filePath, 'utf8')))\n}\n\n/** --play: runs the file headless, replays --script, prints { sends, vars }. */\nfunction playOnce(filePath: string, scriptPath: string, trace: boolean, noLibs = false, explicitFlats: string[] = [], seed?: number): number {\n if (!scriptPath) { process.stderr.write('flatc: --play requires --script <gestures.json>\\n'); return 1 }\n if (!existsSync(scriptPath)) { process.stderr.write(`flatc: script not found: ${scriptPath}\\n`); return 1 }\n let doc: Doc, gestures: Gesture[]\n try { doc = loadDoc(filePath, noLibs, explicitFlats) } catch (e) { process.stderr.write(`flatc: cannot read: ${(e as Error).message}\\n`); return 1 }\n try { gestures = JSON.parse(readFileSync(scriptPath, 'utf8')) as Gesture[] } catch (e) { process.stderr.write(`flatc: invalid JSON script: ${(e as Error).message}\\n`); return 1 }\n if (!Array.isArray(gestures)) { process.stderr.write('flatc: the script must be an array of gestures\\n'); return 1 }\n const res = playHeadless(doc, gestures, { trace, seed })\n if (!trace) process.stdout.write(JSON.stringify(res, null, 2) + '\\n')\n else // --trace: readable log (one gesture per line) → inspection / debug-player.\n for (const s of res.steps ?? []) {\n // `name` · `name=value` · `name{a=1, b=2}` (record payload) — the three shapes the host receives.\n const send = (e: { name: string; value?: number | string; fields?: Record<string, number> }): string =>\n e.fields ? `${e.name}{${Object.entries(e.fields).map(([k, v]) => `${k}=${v}`).join(', ')}}`\n : e.value !== undefined ? `${e.name}=${e.value}` : e.name\n const sends = s.sends.length ? ' sends:[' + s.sends.map(send).join(', ') + ']' : ''\n const vars = Object.entries(s.changed).map(([k, [a, b]]) => `${k}:${JSON.stringify(a)}→${JSON.stringify(b)}`)\n process.stdout.write(`${s.gesture.padEnd(28)}${sends}${vars.length ? ' vars{' + vars.join(' ') + '}' : ''}\\n`)\n }\n // `expect`: any detected mismatch → stderr log + exit code ≠0 (CI self-verification).\n if (res.expectFailures?.length) {\n for (const f of res.expectFailures) process.stderr.write(`flatc: ✗ ${f}\\n`)\n return 1\n }\n return 0\n}\n\n/** Parses `--at`: \"k=v[,k2=v2]\" → variable table (state override for rendering). */\nfunction parseVars(spec: string, into: Record<string, number>): void {\n for (const pair of spec.split(',')) {\n const i = pair.indexOf('=')\n if (i < 0) continue\n const k = pair.slice(0, i).trim(); const v = Number(pair.slice(i + 1).trim())\n if (k && Number.isFinite(v)) into[k] = v\n }\n}\n\n/** `--scale auto`: pick a resolution factor from the canvas size — enlarge small/thin content (so fine\n * filaments stay legible) while leaving large content at 1×. `max(160/long, 48/short)`, clamped [1, 8]. */\nfunction autoScale(w: number, h: number): number {\n const maxSide = Math.max(1, w, h), minSide = Math.max(1, Math.min(w, h))\n return Math.max(1, Math.min(8, Math.round(Math.max(160 / maxSide, 48 / minSide))))\n}\n\n/** Renders a Doc to a PNG file (headless skia). Async (SVG decode + raster). Shared by --render and --preview. */\nasync function renderDocToFile(doc: Doc, outPath: string, frame: number, vars: Record<string, number>, scale: number, steps: number, params: Record<string, string> = {}): Promise<number> {\n try {\n const { renderDocToPng } = await import('./render')\n const png = await renderDocToPng(doc, { frame, vars: Object.keys(vars).length ? vars : undefined, scale, steps: steps || undefined, params: Object.keys(params).length ? params : undefined })\n writeFileSync(outPath, png)\n } catch (e) {\n process.stderr.write(`flatc: render failed: ${(e as Error).message}\\n`)\n if (process.env.FLATC_DEBUG && (e as Error).stack) process.stderr.write(`${(e as Error).stack}\\n`) // FLATC_DEBUG=1 → full stack for diagnosis\n return 1\n }\n process.stdout.write(`flatc: ${basename(outPath)} ✓ ${doc.width}×${doc.height} ×${scale}${frame ? ` · frame ${frame}` : ''}${steps ? ` · ${steps} step(s)` : ''}${Object.keys(vars).length ? ` · ${Object.entries(vars).map(([k, v]) => `${k}=${v}`).join(' ')}` : ''}\\n`)\n return 0\n}\n\n/** --render: renders the file to PNG (headless skia). Async (SVG decode + raster). */\nasync function renderOnce(filePath: string, out: string, frame: number, vars: Record<string, number>, scale: number, steps: number, scaleAuto: boolean, noLibs = false, params: Record<string, string> = {}, explicitFlats: string[] = []): Promise<number> {\n let doc: Doc\n // `--no-libs` was parsed and then not passed on, so `--render` still auto-discovered the neighbouring\n // .flat files -- and failed on one, advising the very flag that had been given. Same hole in `--play`.\n try { doc = loadDoc(filePath, noLibs, explicitFlats) } catch (e) { process.stderr.write(`flatc: cannot read: ${(e as Error).message}\\n`); return 1 }\n // A frame past the end WRAPS silently, so a render can show frame 20 while the caller asked for 200\n // and reads the result as a renderer bug. It cost exactly that once.\n const dur = doc.timeline?.durationFrames ?? 0\n if (dur > 0 && frame >= dur) process.stderr.write(`flatc: --frame ${frame} is past the timeline (durationFrames ${dur}); the playhead WRAPS, so this renders frame ${frame % dur}\\n`)\n const outPath = out ? resolve(out) : join(dirname(filePath), basename(filePath, extname(filePath)) + '.png')\n return renderDocToFile(doc, outPath, frame, vars, scaleAuto ? autoScale(doc.width, doc.height) : scale, steps, params)\n}\n\nconst gcd = (a: number, b: number): number => { a = Math.abs(a); b = Math.abs(b); while (b) { const t = a % b; a = b; b = t } return a || 1 }\nconst lcm = (a: number, b: number): number => Math.max(1, Math.round((a / gcd(a, b)) * b))\n\n/** Every instance in a symbol's subtree (descends into local groups; does NOT cross into other symbols). */\nfunction* instancesOf(sym: SymbolDef): Generator<Instance> {\n const stack: Item[] = []\n for (const l of sym.layers) stack.push(...l.items)\n for (let it = stack.pop(); it; it = stack.pop()) {\n if (isInstance(it)) yield it\n else if (isGroup(it)) for (const l of it.layers) stack.push(...l.items)\n }\n}\n\n/**\n * Preview window length (frames). A `synced` instance loops on the PARENT's frame, so the symbol's own\n * duration suffices — that's the historical behavior, kept for every symbol with no MovieClip descendant.\n * But an `independent` (`loop`) descendant runs on its OWN duration, immune to the parent's wrap: to SHOW it\n * loop CLEANLY the window must be a common multiple of every such clip's duration (and the root's) — its\n * \"loop seam\". `once` clips only need the window to REACH their last frame (they hold afterwards). We walk\n * the subtree, fold each independent clip's duration into the seam (LCM), and extend past the longest clip,\n * keeping the window a whole number of seams. Capped, so coprime durations can't blow the window up.\n */\nfunction previewDuration(root: SymbolDef, symbols: SymbolDef[]): number {\n const byId = new Map(symbols.map((s) => [s.id, s]))\n const seen = new Set<string>()\n let seam = Math.max(1, root.timeline?.durationFrames ?? 1) // clean-loop period (multiple of every loop clip)\n let longest = seam // window must reach the last frame of any clip\n const walk = (sym: SymbolDef | undefined): void => {\n if (!sym || seen.has(sym.id)) return\n seen.add(sym.id)\n for (const it of instancesOf(sym)) {\n const child = byId.get(it.symbolId)\n const cdur = Math.max(1, child?.timeline?.durationFrames ?? 1)\n const mode = it.playback?.mode\n if (mode === 'independent') { seam = lcm(seam, cdur); longest = Math.max(longest, cdur) }\n else if (mode === 'once') longest = Math.max(longest, cdur)\n walk(child)\n }\n }\n walk(root)\n let dur = seam\n while (dur < longest) dur += seam // keep a clean loop seam while covering the longest MovieClip\n return Math.min(dur, 6000) // hard cap (~4 min @24fps) against a pathological coprime-duration blow-up\n}\n\n/**\n * Wraps ONE symbol from a `.flat` library into a minimal, playable Doc: a single centered instance on a\n * canvas auto-sized to the symbol's frame-0 bounds (+ padding). Lets you preview a library asset without\n * hand-authoring a wrapper `.flatink`. The root timeline borrows the symbol's own fps and a duration long\n * enough to show every nested MovieClip (`loop`/`once`) loop cleanly (`previewDuration`). Throws if the lib\n * is empty or the named symbol is missing.\n */\nfunction buildPreviewDoc(flatPath: string, symbolName: string, pad: number, bboxMode: 'all' | 'frame0', setSpec: Record<string, string> = {}): { doc: Doc; symbol: SymbolDef; others: string[] } {\n const { symbols } = parseFlatLib(readFileSync(flatPath, 'utf8'))\n if (!symbols.length) throw new Error('no symbols found in this .flat library')\n let symbol = symbols[0]\n if (symbolName) {\n const found = symbols.find((s) => s.name === symbolName)\n if (!found) throw new Error(`symbol \"${symbolName}\" not found — available: ${symbols.map((s) => s.name).join(', ')}`)\n symbol = found\n }\n\n const instance: Instance = { id: 'preview_instance', kind: 'instance', name: symbol.name, transform: { ...IDENTITY }, symbolId: symbol.id }\n const layer: Layer = { id: 'preview_layer', name: 'preview', visible: true, locked: false, opacity: 1, items: [instance] }\n const tl = symbol.timeline\n const dur = previewDuration(symbol, symbols)\n const timeline = { fps: tl?.fps ?? 24, durationFrames: dur, tracks: [] }\n // ALL symbols stay in the library (the chosen one may instance the others) — only `instance` is on stage.\n let doc: Doc = { width: 1, height: 1, layers: [layer], symbols, timeline }\n\n // Auto-size & center. Default `--bbox all`: UNION over every frame of the symbol (sub-timelines NOT\n // frozen) so motion that drifts/rotates/grows is never clipped. `--bbox frame0` keeps the old frame-0\n // measure (+`--pad` to absorb motion).\n // Sample at most ~240 evenly-spaced frames (+ the last) so a long/abusive `durationFrames` can't blow\n // up memory/time; a sparse union is virtually as tight as every-frame for real motion.\n const sampleFrames = (n: number): number[] => {\n const step = Math.max(1, Math.ceil(n / 240))\n const fs: number[] = []\n for (let f = 0; f < n; f += step) fs.push(f)\n if (fs[fs.length - 1] !== n - 1) fs.push(n - 1)\n return fs\n }\n const bb = bboxMode === 'all' ? containerBBoxUnion(doc, instance, sampleFrames(dur)) : containerBBox(doc, instance, 0)\n if (bb) {\n doc = { ...doc, width: Math.max(1, Math.ceil(bb.maxX - bb.minX) + pad * 2), height: Math.max(1, Math.ceil(bb.maxY - bb.minY) + pad * 2) }\n instance.transform = { ...IDENTITY, e: pad - bb.minX, f: pad - bb.minY }\n } else {\n doc = { ...doc, width: 512, height: 512 } // empty/degenerate symbol: fall back to a fixed centered stage\n instance.transform = { ...IDENTITY, e: 256, f: 256 }\n }\n\n // `--set param=value`: set the preview instance's exposed params (call-site values, raw literals). The\n // renderer resolves them per the symbol's ParamDef / state machines (color → fill, number/bool → scope,\n // state name → driven frame). A light validation warns on names the symbol does not expose.\n const entries = Object.entries(setSpec)\n if (entries.length) {\n const known = new Set([...(symbol.states ?? []).map((s) => s.param), ...(symbol.params ?? []).map((p) => p.name)])\n for (const [k, v] of entries) {\n if (!known.has(k)) process.stderr.write(`flatc: --set ${k}: \"${symbol.name}\" exposes no such param (${[...known].join(', ') || 'none'})\\n`)\n else (instance.params ??= {})[k] = v\n }\n }\n return { doc, symbol, others: symbols.filter((s) => s !== symbol).map((s) => s.name) }\n}\n\n/** --preview: wraps one symbol of a `.flat` into a playable Doc, then writes a .flatpack (default) or a PNG (with --render). */\nasync function previewOnce(flatPath: string, symbolName: string, out: string, frame: number, vars: Record<string, number>, scale: number, steps: number, doRender: boolean, pad: number, bboxMode: 'all' | 'frame0', setSpec: Record<string, string>, scaleAuto: boolean): Promise<number> {\n let built: { doc: Doc; symbol: SymbolDef; others: string[] }\n try { built = buildPreviewDoc(flatPath, symbolName, pad, bboxMode, setSpec) }\n catch (e) { process.stderr.write(`flatc: ${(e as Error).message}\\n`); return 1 }\n const { doc, symbol, others } = built\n const stem = `${basename(flatPath, extname(flatPath))}.${symbol.name}`\n let code: number\n if (doRender) {\n // auto scale from the CONTENT size (minus the padding), so thin filaments still trigger an upscale.\n const sc = scaleAuto ? autoScale(doc.width - 2 * pad, doc.height - 2 * pad) : scale\n code = await renderDocToFile(doc, out ? resolve(out) : join(dirname(flatPath), stem + '.png'), frame, vars, sc, steps)\n } else {\n const outPath = out ? resolve(out) : join(dirname(flatPath), stem + '.flatpack')\n writeFileSync(outPath, packToJSON(doc))\n process.stdout.write(`flatc: ${basename(outPath)} ✓ preview of \"${symbol.name}\" · ${doc.width}×${doc.height} · play it in the browser player\\n`)\n code = 0\n }\n if (!symbolName && others.length) process.stderr.write(`flatc: previewed \"${symbol.name}\"; other symbols in this lib: ${others.join(', ')} (pick with --symbol <name>)\\n`)\n return code\n}\n\n/**\n * `--since <version>`: what these sources draw DIFFERENTLY from that version, for an author deciding what\n * to do about it once, when upgrading. Read from the SOURCE, so every finding has its line.\n *\n * One change is recorded: until 0.36 a path made only of lines was rounded at its gentle turns; it is now\n * drawn straight. Each such path is listed with how far its outline moves — a soft hill written as four\n * points wants `smooth` back, a hexagon does not. This is a report, not a warning of `--check`: a polyline\n * that is MEANT straight has nothing to add to say so, and would be warned about for ever.\n */\nfunction sinceReport(files: string[], since: string, all = false): number {\n const [maj, min] = since.split('.').map(Number)\n if (!Number.isFinite(maj) || !Number.isFinite(min)) { process.stderr.write(`flatc: --since takes a version, e.g. --since 0.35 (got \"${since}\")\\n`); return 1 }\n if (maj > 0 || min >= 36) { process.stdout.write(`flatc: nothing is drawn differently since ${since}\\n`); return 0 }\n let open = 0, closed = 0, slight = 0\n for (const file of files) {\n let src: string\n try { src = readFileSync(file, 'utf8') } catch (e) { process.stderr.write(`flatc: cannot read ${file}: ${(e as Error).message}\\n`); return 1 }\n // Every `path \"…\"` of the file that does not already say `smooth` (a text laid `along path \"…\"` is not a shape).\n for (const m of src.matchAll(/(?<!along\\s)\\bpath\\s+\"([^\"]*)\"(?!\\s+smooth\\b)/g)) {\n // Read as it was BEFORE (free-hand material) to measure what smoothing did to it.\n for (const sub of parsePathData(m[1], { smooth: true }).subpaths) {\n if (sub.segments.some((sg) => sg.inHandle || sg.outHandle) || !softVertexCount(sub)) continue // has a curve, or only sharp corners\n const moved = smoothingDeviation(sub)\n const xs = sub.segments.map((sg) => sg.anchor.x), ys = sub.segments.map((sg) => sg.anchor.y)\n const size = Math.hypot(Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys))\n // Listed when the outline moves by 1% of the shape's size or more — RELATIVE, because artwork is often\n // drawn in a large space and scaled down by its container. Below that the two renderings are a few\n // pixels apart at most; `--all` lists those too.\n if (moved <= 0 || size <= 0) continue\n if (moved < size * 0.01 && !all) { slight++; continue }\n const line = src.slice(0, m.index).split('\\n').length\n if (sub.closed) closed++; else open++\n process.stdout.write(`${file}:${line}: ${sub.closed ? 'closed' : 'open'} path of ${sub.segments.length} points was rounded before 0.36 and is now straight — it moves by up to ${Math.round(moved * 10) / 10} units (${Math.round((1000 * moved) / (size || 1)) / 10}% of its size). Write \\`smooth\\` after its path data to keep the curve.\\n`)\n }\n }\n }\n const n = open + closed\n const rest = slight ? ` ${slight} more move by less than 1% of their size (\\`--all\\` lists them).` : ''\n process.stdout.write(n\n ? `flatc: ${n} path(s) of lines drawn differently since ${since}: ${open} open, ${closed} closed.${rest} Nothing was modified. (Rounded rectangles and paths mixing lines with curves changed too — their straight sides no longer bulge — and are not listed.)\\n`\n : `flatc: no path of lines is drawn visibly differently since ${since} in ${files.length} file(s).${rest}\\n`)\n return 0\n}\n\nexport function run(argv: string[]): number | Promise<number> {\n const args = argv.slice(2)\n let out = '', scriptPath = '', symbolName = ''\n let checkOnly = false, doFix = false, doWatch = false, doPlay = false, doRender = false, doTrace = false, doPreview = false, noLibs = false\n let frame = 0, scale = 2, steps = 0, pad = 24\n let scaleAuto = false\n let seed: number | undefined\n let since = ''\n let bboxMode: 'all' | 'frame0' = 'all'\n let assetMode: AssetMode = 'inline'\n const vars: Record<string, number> = {}\n const setSpec: Record<string, string> = {} // `--set param=value` (state name or number) for --preview\n const positional: string[] = []\n for (let i = 0; i < args.length; i++) {\n const a = args[i]\n if (a === '-o' || a === '--out') out = args[++i] ?? ''\n else if (a === '--script') scriptPath = resolve(args[++i] ?? '')\n else if (a === '--assets') assetMode = args[++i] === 'external' ? 'external' : 'inline'\n else if (a === '--check') checkOnly = true\n else if (a === '--fix') { doFix = true; checkOnly = true }\n else if (a === '--no-libs') noLibs = true\n else if (a === '--watch') doWatch = true\n else if (a === '--play') doPlay = true\n else if (a === '--trace') doTrace = true\n else if (a === '--render') doRender = true\n else if (a === '--preview') doPreview = true\n else if (a === '--symbol') symbolName = args[++i] ?? ''\n else if (a === '--pad') pad = Math.max(0, Number(args[++i] ?? '24') || 0)\n else if (a === '--bbox') bboxMode = args[++i] === 'frame0' ? 'frame0' : 'all'\n else if (a === '--frame') frame = Number(args[++i] ?? '0') || 0\n else if (a === '--since') since = args[++i] ?? ''\n else if (a === '--all') { /* read by --since */ }\n else if (a === '--seed') { const v = Number(args[++i]); if (Number.isFinite(v)) seed = v }\n else if (a === '--steps') steps = Math.max(0, Number(args[++i] ?? '0') || 0)\n else if (a === '--scale') { const v = args[++i] ?? '2'; if (v === 'auto') scaleAuto = true; else scale = Number(v) || 2 }\n else if (a === '--at') parseVars(args[++i] ?? '', vars)\n else if (a === '--set') for (const pair of (args[++i] ?? '').split(',')) { const j = pair.indexOf('='); if (j > 0) setSpec[pair.slice(0, j).trim()] = pair.slice(j + 1).trim() }\n else if (a === '-h' || a === '--help') { printHelp(); return 0 }\n else positional.push(a)\n }\n if (!positional.length) { printHelp(); return 1 }\n\n const filePath = resolve(positional[0])\n if (!existsSync(filePath)) { process.stderr.write(`flatc: not found: ${filePath}\\n`); return 1 }\n\n if (since) return sinceReport(positional.map((f) => resolve(f)), since, args.includes('--all'))\n const explicitFlats = positional.slice(1)\n if (doPreview) return previewOnce(filePath, symbolName, out, frame, vars, scale, steps, doRender, pad, bboxMode, setSpec, scaleAuto)\n if (doRender) return renderOnce(filePath, out, frame, vars, scale, steps, scaleAuto, noLibs, setSpec, explicitFlats)\n if (doPlay) return playOnce(filePath, scriptPath, doTrace, noLibs, explicitFlats, seed)\n // `--check <library>.flat`: a `.flat` first positional is an asset LIB, not a program → lint via parseFlatLib\n // (the following positionals are more `.flat` libs to merge). Every other path is unchanged.\n const action: () => number = checkOnly && filePath.endsWith('.flat')\n ? () => checkFlatLibs([filePath, ...explicitFlats])\n : () => compileOnce(filePath, explicitFlats, out, checkOnly, assetMode, noLibs, doFix)\n if (doWatch) {\n const code = action()\n const baseDir = dirname(filePath)\n let timer: ReturnType<typeof setTimeout> | undefined\n // We ignore changes to the OUTPUT (.flatpack) — otherwise writing it would re-trigger the compile in a loop.\n watch(baseDir, { recursive: false }, (_e, filename) => {\n if (filename && (filename.endsWith('.flatpack') || filename.endsWith('.flatpack.json'))) return\n clearTimeout(timer); timer = setTimeout(action, 80)\n })\n process.stdout.write(`flatc: watching ${baseDir} … (Ctrl+C to stop)\\n`)\n return code\n }\n return action()\n}\n"],"mappings":"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","names":["f"]}
1
+ {"version":3,"sources":["../../src/cli/flatc.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// flatc — the \"modern SWF\" command-line compiler.\n//\n// The VSCode-first flow: you write a `.flatink` program (composition + logic, DSL)\n// + `.flat` asset libs (visuals, exported by the editor) + media, then:\n//\n// flatc game.flatink hero.flat decor.flat -o game.flatpack\n//\n// → a single `.flatpack` (the baked Doc as JSON) that the player runs. Media referenced\n// by `asset \"id\" \"path\" kind` are EMBEDDED (paths relative to the program).\n// ─────────────────────────────────────────────────────────────────────────────\nimport { readFileSync, writeFileSync, existsSync, readdirSync, watch, mkdirSync, copyFileSync } from 'node:fs'\nimport { resolve, dirname, basename, extname, join, relative, isAbsolute } from 'node:path'\nimport { compileFlatpack, packToJSON, type MediaMap } from '../compile'\nimport { parseProgramFull, parseFlatLib } from '@flatkit/engine/flatFormat'\nimport { hasPackage } from '@flatkit/engine/stdlib'\nimport { parseUnits } from '@flatkit/engine/dsl'\nimport { sanitizeDoc } from '@flatkit/engine/validateDoc'\nimport { unitsToFunctions } from '@flatkit/engine/scriptDoc'\nimport { containerBBox, containerBBoxUnion } from '@flatkit/engine/groups'\nimport { isInstance, isGroup } from '@flatkit/engine/layers'\nimport { IDENTITY } from '@flatkit/engine/transform'\nimport { parsePathData } from '@flatkit/engine/svgPath'\nimport { softVertexCount, smoothingDeviation } from '@flatkit/engine/path'\nimport { lintDocReport, docHasErrors } from '../programDoc'\nimport { applyFixes, formatDiagnostics, programDiagnostics, repairLoop } from '../check'\nimport { FlatSyntaxError } from '@flatkit/engine/flatFormat'\nimport { playHeadless, type Gesture } from '@flatkit/player/debug'\nimport type { FuncDef } from '@flatkit/engine/actions'\nimport type { Doc, Instance, Item, Layer, SymbolDef } from '@flatkit/types'\n\nconst MIME: Record<string, string> = {\n '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif',\n '.webp': 'image/webp', '.svg': 'image/svg+xml', '.avif': 'image/avif',\n '.mp3': 'audio/mpeg', '.wav': 'audio/wav', '.ogg': 'audio/ogg', '.m4a': 'audio/mp4',\n // Fonts (RFC 8081 media types) — matches the editor's import (`font/woff2` default, FontFace API).\n '.woff2': 'font/woff2', '.woff': 'font/woff', '.ttf': 'font/ttf', '.otf': 'font/otf',\n // NB: video (.mp4/.webm/…) intentionally omitted — no video runtime in the editor or player yet.\n}\nconst mimeFor = (path: string): string => MIME[extname(path).toLowerCase()] ?? 'application/octet-stream'\n\n/**\n * True if `target` resolves to a file inside `baseDir` (or baseDir itself). Guards against a hostile\n * program escaping its folder via `../` in a media path or a `use` package name — compiling an untrusted\n * `.flatink` must never read (and embed) arbitrary host files.\n */\nfunction isWithin(baseDir: string, target: string): boolean {\n const rel = relative(baseDir, target)\n return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel))\n}\n\nfunction printHelp(): void {\n process.stdout.write(`flatc — compile a FlatInk program into a .flatpack\n\nUsage:\n flatc <program.flatink> [assets.flat …] [-o output.flatpack]\n flatc <program.flatink> --watch\n flatc --play <program.flatink | scene.flatpack> --script <gestures.json>\n flatc --render <program.flatink | scene.flatpack> -o out.png [--frame N] [--at k=v[,k2=v2]] [--scale S]\n flatc --preview <library.flat> [--symbol NAME] [-o out.flatpack | --render -o out.png]\n flatc <library.flat> [more.flat …] --check\n\n program.flatink the program (composition + logic, text DSL)\n assets.flat visual asset libs (default: every .flat in the program's folder)\n -o, --out output file (default: <program>.flatpack — the CANONICAL name, JSON inside)\n --assets MODE media baking: 'inline' (default, base64 in the .flatpack) or 'external'\n (sidecar <out>.assets/ folder; asset.data = relative key — serve the folder and\n play with sameOriginAssetResolver(<flatpackUrl>))\n --check semantic lint only (no .flatpack); exits ≠0 on ERROR (warnings do not stop). Lints a\n program .flatink OR an asset library .flat (per-symbol; several .flat are merged)\n --fix apply the MECHANICAL repairs the diagnostics carry (missing separator, run-on line)\n and re-check, iterating. Writes the file AT MOST ONCE, and never a text it has not\n re-checked; a file it cannot repair is left untouched, mtime included. Implies --check.\n --no-libs do NOT auto-discover the .flat files sitting next to the program (use it in a working\n folder, where a neighbouring scratch file is not a dependency)\n --watch recompile on every change in the folder (agent → player loop)\n --play run the file WITHOUT a canvas, replay --script and print { sends, vars } (JSON)\n --since <ver> what the given files (.flatink / .flat, as many as you like) draw DIFFERENTLY from\n that version — a one-shot report for an upgrade, with file:line. Recorded so far:\n before 0.36 a path made only of lines was rounded at its gentle turns. Lists the\n paths that move by 1% of their size or more, and the open lines that move by 2\n units or more; add --all for the slighter ones\n --seed N (with --play) seed of random(). A replay is always seeded (default 1): the same script\n gives the same result twice; another N gives another draw\n --settle N (with --play, --render --script) simulation steps after each pointer event (default 1:\n a press, a move, a release each take a frame, so \"every frame\" sees the drag;\n 0 = the instantaneous replay). \"settle\": N on a gesture sets it for that one\n --trace (with --play) HUMAN-READABLE log per gesture: emitted sends + variable diff (debug)\n --script <f> JSON gesture script: [{ \"type\": \"down|move|up|cancel\", \"x\", \"y\" }, { \"type\": \"set\", \"name\", \"value\" }, { \"type\": \"wait\", \"frames\": N }, { \"type\": \"wheel\", \"dy\": N }]\n semantic (by NAME, the engine resolves coords): { \"type\": \"drag\", \"source\", \"target\" } · { \"type\": \"tap\", \"target\" } (or \"x\", \"y\": a point)\n · { \"type\": \"scratch\", \"target\" } (sweeps a reveal zone) · { \"type\": \"connect\", \"source\", \"target\" } (pulls a link wire)\n { \"type\": \"key\", \"name\": \"ArrowRight\", \"frames\": N } holds a key (keys.<name> = 1) N steps, then releases it\n \"wait\" lets the simulation run N fixed steps (60 Hz): \"every frame\" + playhead advance like in real playback\n \"expect\" self-verifies: { \"type\": \"expect\", \"sends\": [\"done\"], \"vars\": { \"score\": 3 } } → exits ≠0 on mismatch\n (sends = sequence of names emitted SINCE the last expect; vars = current state). Great in CI.\n --render render a PNG IMAGE (headless skia): see what we draw (positioning). With --script,\n replays the gestures first and renders the state they reach; a { \"type\": \"shot\",\n \"name\": \"x\" } gesture writes <out>.x.png at that point; a failed \"expect\" exits ≠0\n --preview wrap ONE symbol of a <library.flat> into a playable Doc (a single centered instance on an\n auto-sized stage) → a .flatpack to drop in the browser player, or a PNG with --render.\n No wrapper .flatink to author by hand. Output defaults to <library>.<symbol>.flatpack/.png\n --symbol NAME (with --preview) which symbol to wrap (default: the first in the lib)\n --bbox all|frame0 (with --preview) auto-size to the UNION over all frames (default 'all', no clipping of\n drifting/rotating/growing motion) or just frame 0 ('frame0', the old behavior)\n --pad N (with --preview) padding in px around the symbol's bounds (default 24)\n --set p=v[,p2=v2] set a SYMBOL's exposed params (works with --preview AND --render); a state param\n takes a state NAME\n or a number (e.g. --set door=open), others take a number → baked into the preview\n --frame N (with --render) target frame (default 0)\n --at k=v[,k2=v2] (with --render) force variables → capture a given state (e.g. a step of an escape)\n --steps N (with --render) run N fixed sim steps (60 Hz, every-frame) BEFORE capture → see a\n stateful act unfold without forcing every derived variable by hand in --at\n --scale S|auto (with --render) resolution factor (default 2); 'auto' picks one from the canvas size\n (enlarges small/thin assets so fine filaments stay legible, large assets stay 1x)\n -h, --help this help\n\nMedia referenced by 'asset \"id\" \"path\" kind' are embedded (paths relative to the program).\n`)\n}\n\n/** How media is baked: `inline` = base64 data-URI in the .flatpack; `external` = relative key + sidecar files. */\ntype AssetMode = 'inline' | 'external'\ntype MediaCopy = { src: string; key: string } // external mode: source file → relative key (forward slashes)\ntype BuildResult = { doc: Doc; flatLibs: number; packages: number; media: number; mediaCopies: MediaCopy[]; src: string }\n\n/**\n * Reads a `.flatink`, resolves libs/packages/media, compiles → standalone Doc. Throws on compile error.\n * `assetMode`: `inline` embeds each media as a base64 data-URI (default); `external` keeps `asset.data` as a\n * relative key (`<assetsDir>/<path>`) and returns the files to copy beside the .flatpack (no base64 bloat).\n */\nfunction buildDocFromProgram(programPath: string, explicitFlats: string[] = [], assetMode: AssetMode = 'inline', assetsDir = '', noLibs = false, srcOverride?: string): BuildResult {\n const baseDir = dirname(programPath)\n // `srcOverride` compiles a CANDIDATE text as if it were the file, without writing it. `--fix` needs it:\n // it must re-check each repair before committing anything, and writing-then-reverting leaves a window\n // where the author's file holds a version we already know we do not want.\n const programSrc = srcOverride ?? readFileSync(programPath, 'utf8')\n const prog = parseProgramFull(programSrc)\n\n // .flat libs: explicit if provided, otherwise auto-discover the .flat files in the program's folder\n // (`--no-libs` opts out — in a working folder, a neighbouring scratch file is not a dependency).\n const flatPaths = new Set<string>(explicitFlats.map((p) => resolve(p)))\n if (!flatPaths.size && !noLibs) for (const f of readdirSync(baseDir).filter((f) => f.endsWith('.flat'))) flatPaths.add(join(baseDir, f))\n\n // PACKAGES: non-stdlib `use \"x\"` → local files inlined (x.flatink = functions, x.flat = symbols).\n const pkgFunctions: FuncDef[] = []\n const localResolved = new Set<string>()\n for (const name of prog.imports ?? []) {\n if (hasPackage(name)) continue // stdlib → left as a reference\n const fink = join(baseDir, name + '.flatink')\n const fflat = join(baseDir, name + '.flat')\n if (!isWithin(baseDir, fink) || !isWithin(baseDir, fflat)) { process.stderr.write(`flatc: package outside the program folder (ignored): \"${name}\"\\n`); continue }\n let found = false\n if (existsSync(fink)) { for (const f of unitsToFunctions(parseUnits(readFileSync(fink, 'utf8')).units)) pkgFunctions.push(f, { ...f, name: `${name}.${f.name}` }); found = true }\n if (existsSync(fflat)) { flatPaths.add(fflat); found = true }\n if (found) localResolved.add(name)\n else process.stderr.write(`flatc: package not found: \"${name}\" (neither stdlib, nor ${name}.flatink / ${name}.flat)\\n`)\n }\n // Parse each lib HERE so a failure names the FILE. Libs are auto-discovered from the folder, so the one\n // that fails is often a neighbour the author never mentioned — a bare \"compile error: …\" sent them hunting\n // through their own program for a fault that was never in it.\n const assetSrcs = [...flatPaths].map((p) => {\n const src = readFileSync(p, 'utf8')\n try { parseFlatLib(src) } catch (e) { throw new Error(`${basename(p)}: ${(e as Error).message} (pass --no-libs to ignore the .flat files sitting next to the program)`, { cause: e }) }\n if (programConstructsIn(src).length) process.stderr.write(`flatc: ${basename(p)}: looks like a PROGRAM saved as .flat — loaded as a symbol library, so its scene/object blocks are ignored. Rename it to .flatink, or pass --no-libs.\\n`)\n return src\n })\n\n // Media: each `asset … \"path\" …` resolved relative to the program. `inline` → base64 data-URI baked in;\n // `external` → `asset.data` becomes a relative key and the file is copied next to the .flatpack.\n const media: MediaMap = {}\n const mediaCopies: MediaCopy[] = []\n for (const a of prog.assets ?? []) {\n const mp = resolve(baseDir, a.data)\n if (!isWithin(baseDir, mp)) { process.stderr.write(`flatc: media outside the program folder (ignored): ${a.data}\\n`); continue }\n if (!existsSync(mp)) { process.stderr.write(`flatc: missing media (ignored): ${a.data}\\n`); continue }\n const mime = mimeFor(mp)\n if (assetMode === 'external') {\n const key = `${assetsDir}/${a.data.replace(/\\\\/g, '/')}` // URL-form relative key (resolved by the host)\n media[a.data] = { mime, data: key }\n mediaCopies.push({ src: mp, key })\n } else {\n media[a.data] = { mime, data: `data:${mime};base64,${readFileSync(mp).toString('base64')}` }\n }\n }\n\n let doc = compileFlatpack(programSrc, assetSrcs, media)\n if (pkgFunctions.length) doc = { ...doc, functions: [...(doc.functions ?? []), ...pkgFunctions] }\n const stdImports = (doc.imports ?? []).filter(hasPackage)\n doc = { ...doc, imports: stdImports.length ? stdImports : undefined }\n // The source travels with the Doc: the source-level passes (`programDiagnostics`) need the author's text,\n // since a parse-level drop and an `object` block binding to nothing leave no trace in the compiled Doc.\n return { doc, flatLibs: flatPaths.size, packages: localResolved.size, media: Object.keys(media).length, mediaCopies, src: programSrc }\n}\n\n/** Compile once (write or --check). Returns the exit code. */\nfunction compileOnce(programPath: string, explicitFlats: string[], out: string, checkOnly: boolean, assetMode: AssetMode = 'inline', noLibs = false, doFix = false): number {\n const outPath = out ? resolve(out) : join(dirname(programPath), basename(programPath, extname(programPath)) + '.flatpack')\n // External mode: sidecar folder next to the .flatpack, e.g. `game.flatpack` → `game.assets/`.\n const assetsDir = assetMode === 'external' ? basename(outPath, extname(outPath)) + '.assets' : ''\n const build = (src?: string) => buildDocFromProgram(programPath, explicitFlats, assetMode, assetsDir, noLibs, src)\n // `--fix` on a source the parser REJECTS. That case never reaches the diagnostics pass below, and it is\n // exactly where a mechanical repair earns its keep: there is no partial result to work from. Repaired\n // IN MEMORY, one syntax error per turn, so the whole of `--fix` still writes the file at most once.\n let parseRepaired = ''\n if (doFix) {\n let candidate = readFileSync(programPath, 'utf8')\n for (let pass = 0; pass < 10; pass++) {\n try { build(candidate); break } catch (e) {\n if (!(e instanceof FlatSyntaxError) || !e.fix) break\n const { text, applied } = applyFixes(candidate, [{ scope: 'scene', line: e.line, col: e.col, severity: 'error', message: e.message, fix: e.fix }])\n if (!applied) break\n candidate = text\n parseRepaired = candidate\n process.stderr.write(`flatc: --fix: repaired \\`${e.message.split(':')[0]}\\` in ${basename(programPath)}\\n`)\n }\n }\n }\n let built: BuildResult\n try { built = build(parseRepaired || undefined) }\n catch (e) {\n process.stderr.write(`flatc: compile error: ${(e as Error).message}\\n`)\n return 1\n }\n const { doc } = built\n // The SAME pass the `checkProgram` API runs — source-level diagnostics (statements the parser dropped,\n // `object` blocks binding to nothing) merged with the semantic lint of the Doc. Shared on purpose: a\n // check that lives only in the CLI is a check an integrator has to shell out for.\n const diagnostics = programDiagnostics(doc, built.src)\n const report = formatDiagnostics(diagnostics)\n const hasErrors = diagnostics.some((d) => d.severity === 'error')\n // `--fix` applies the MECHANICAL repairs the diagnostics carry (a missing separator, a run-on line),\n // then re-checks. Two rules make it safe to run unattended:\n // • it ITERATES, because repairing one error unmasks the next — a run-on interactor line swallows the\n // statements under it, and their own diagnostics only appear once it is split;\n // • it writes only if the error count strictly DROPS at each pass. An auto-fix that makes things worse\n // must not survive, and the honest way to know is to run the same check again on the result.\n if (doFix) {\n // START from the text the diagnostics were computed on. Falling back to the file would apply edits\n // whose line/col were measured against the parse-repaired text -- a comma insertion survives that,\n // a multi-line replacement does not.\n const original = parseRepaired || readFileSync(programPath, 'utf8')\n const { text, applied: total, first, errors: before } = repairLoop(original, diagnostics, (src) =>\n programDiagnostics(buildDocFromProgram(programPath, explicitFlats, assetMode, assetsDir, noLibs, src).doc, src))\n if (!total && !parseRepaired) {\n // NOT even a rewrite of the identical bytes: a new mtime wakes every watcher pointed at the folder,\n // and a write fails outright on a read-only checkout we had no reason to touch.\n process.stderr.write(`flatc: --fix: nothing to repair mechanically${hasErrors ? ' — the remaining errors need a decision, not a separator\\n' : '\\n'}`)\n } else {\n writeFileSync(programPath, text) // ONE write, of a text this loop has already re-checked\n process.stderr.write(`flatc: --fix: ${total} repair(s) applied to ${basename(programPath)} · errors ${first} → ${before}\\n`)\n return compileOnce(programPath, explicitFlats, out, checkOnly, assetMode, noLibs, false)\n }\n }\n if (checkOnly) {\n if (report) process.stderr.write(report + '\\n')\n if (hasErrors) return 1\n // Success line deliberately avoids the word \"error\" (the report on a FAILURE already prints \"error\" to\n // stderr + exits ≠0) so a `grep error` over the output can't false-positive — the real signal is the exit\n // code; on success, only warnings remain (non-blocking), surfaced as a count.\n const warnings = diagnostics.length\n process.stdout.write(`flatc: check passed ✓${warnings ? ` · ${warnings} warning(s)` : ''}\\n`)\n return 0\n }\n if (report) process.stderr.write(report + '\\n') // compile anyway: diagnostics as warnings\n writeFileSync(outPath, packToJSON(doc))\n const outDir = dirname(outPath)\n for (const c of built.mediaCopies) {\n const dest = join(outDir, ...c.key.split('/'))\n if (!isWithin(outDir, dest)) continue // defensive: never write outside the output folder\n mkdirSync(dirname(dest), { recursive: true })\n copyFileSync(c.src, dest)\n }\n const where = assetMode === 'external' ? ` (external → ${assetsDir}/)` : ''\n process.stdout.write(`flatc: ${basename(outPath)} ✓ ${doc.symbols.length} symbol(s) · ${built.flatLibs} lib(s) · ${built.packages} package(s) · ${built.media} media${where}\\n`)\n return 0\n}\n\n/** PROGRAM-only constructs found in what is supposed to be a `.flat` symbol library. `parseFlatLib` reads a\n * `.flat` as a bag of symbols and silently ignores everything else, so a whole program saved under the wrong\n * extension parsed to \"0 symbol(s)\" and passed `--check` with nothing verified — the worst possible answer\n * for tooling, since a check written against the wrong extension ALWAYS passes and looks like a safety net.\n * Both constructs are anchored at line start: only a real block matches, not a mention inside a string. */\nfunction programConstructsIn(src: string): string[] {\n const found: string[] = []\n if (/^[ \\t]*scene\\s*\\{/m.test(src)) found.push('a `scene { … }` block')\n if (/^[ \\t]*object\\s+\"/m.test(src)) found.push('`object \"…\" { … }` blocks')\n return found\n}\n\n/**\n * `--check` for a `.flat` asset LIBRARY (one or several): parse via `parseFlatLib` (NOT the program parser,\n * which would choke on `symbol`/`params`/`layer`), merge the symbols into an empty-scene Doc, and run the SAME\n * Doc lint as a program -> per-symbol diagnostics (`[<Symbol>] line:col: …`). Reuses `lintDocReport`/\n * `docHasErrors`, so every existing check (params-in-expr, undeclared color param in a paint, unknown\n * functions/objects…) applies for free, with the same `[scope] line:col: level: msg` format and exit code.\n * No `behaviorDiagnostics` (those are scene-level — a pure lib has no scene).\n */\nfunction checkFlatLibs(flatPaths: string[]): number {\n const symbols: SymbolDef[] = []\n for (const p of flatPaths) {\n let src: string\n try { src = readFileSync(p, 'utf8') } catch (e) { process.stderr.write(`flatc: cannot read: ${(e as Error).message}\\n`); return 1 }\n const disguised = programConstructsIn(src)\n if (disguised.length) { // a PROGRAM saved as `.flat` — see programConstructsIn\n process.stderr.write(`flatc: ${basename(p)}: this is a PROGRAM, not a symbol library — it contains ${disguised.join(' and ')}, which a .flat never carries and which --check therefore does NOT verify. Rename it to .flatink.\\n`)\n return 1\n }\n try { symbols.push(...parseFlatLib(src).symbols) } // ids are uid-unique across calls -> safe to merge libs\n catch (e) { process.stderr.write(`flatc: ${basename(p)}: ${(e as Error).message}\\n`); return 1 } // a malformed lib -> a clean parse error (not \"[scene] …\")\n }\n const doc: Doc = { width: 1, height: 1, timeline: { fps: 24, durationFrames: 1, tracks: [] }, variables: {}, layers: [], symbols }\n const report = lintDocReport(doc) // scene scope is empty -> only per-symbol diagnostics\n if (report) process.stderr.write(report + '\\n')\n if (docHasErrors(doc)) return 1\n // \"check passed\", NOT \"no errors\" — the word \"error\" must not appear on success (a `grep error` trap); the\n // exit code is the signal, and any remaining lines are warnings (non-blocking), reported as a count.\n const warnings = report ? report.split('\\n').filter(Boolean).length : 0\n process.stdout.write(`flatc: check passed ✓ ${symbols.length} symbol(s)${warnings ? ` · ${warnings} warning(s)` : ''}\\n`)\n return 0\n}\n\n/** Loads a Doc from a `.flatink` (compiled) or a `.flatpack`/`.flatpack.json` (already-baked JSON). */\nfunction loadDoc(filePath: string, noLibs = false, explicitFlats: string[] = []): Doc {\n // The `.flat` libraries named on the command line are part of the program, exactly as for a compile:\n // dropping them here left every instance of theirs unresolved — an empty scene, with `--check` green.\n if (filePath.endsWith('.flatink')) return buildDocFromProgram(filePath, explicitFlats, 'inline', '', noLibs).doc\n // .flatpack (canonical) or .flatpack.json (alias): untrusted JSON → normalize before use.\n return sanitizeDoc(JSON.parse(readFileSync(filePath, 'utf8')))\n}\n\n/** --play: runs the file headless, replays --script, prints { sends, vars }. */\nfunction playOnce(filePath: string, scriptPath: string, trace: boolean, noLibs = false, explicitFlats: string[] = [], seed?: number, settle?: number): number {\n if (!scriptPath) { process.stderr.write('flatc: --play requires --script <gestures.json>\\n'); return 1 }\n if (!existsSync(scriptPath)) { process.stderr.write(`flatc: script not found: ${scriptPath}\\n`); return 1 }\n let doc: Doc, gestures: Gesture[]\n try { doc = loadDoc(filePath, noLibs, explicitFlats) } catch (e) { process.stderr.write(`flatc: cannot read: ${(e as Error).message}\\n`); return 1 }\n try { gestures = JSON.parse(readFileSync(scriptPath, 'utf8')) as Gesture[] } catch (e) { process.stderr.write(`flatc: invalid JSON script: ${(e as Error).message}\\n`); return 1 }\n if (!Array.isArray(gestures)) { process.stderr.write('flatc: the script must be an array of gestures\\n'); return 1 }\n const res = playHeadless(doc, gestures, { trace, seed, settle })\n // On stderr, and no effect on the exit code: the JSON on stdout stays what a caller parses.\n for (const w of res.warnings ?? []) process.stderr.write(`flatc: warning: ${w}\\n`)\n if (!trace) process.stdout.write(JSON.stringify(res, null, 2) + '\\n')\n else // --trace: readable log (one gesture per line) → inspection / debug-player.\n for (const s of res.steps ?? []) {\n // `name` · `name=value` · `name{a=1, b=2}` (record payload) — the three shapes the host receives.\n const send = (e: { name: string; value?: number | string; fields?: Record<string, number> }): string =>\n e.fields ? `${e.name}{${Object.entries(e.fields).map(([k, v]) => `${k}=${v}`).join(', ')}}`\n : e.value !== undefined ? `${e.name}=${e.value}` : e.name\n const sends = s.sends.length ? ' sends:[' + s.sends.map(send).join(', ') + ']' : ''\n const vars = Object.entries(s.changed).map(([k, [a, b]]) => `${k}:${JSON.stringify(a)}→${JSON.stringify(b)}`)\n process.stdout.write(`${s.gesture.padEnd(28)}${sends}${vars.length ? ' vars{' + vars.join(' ') + '}' : ''}\\n`)\n }\n // `expect`: any detected mismatch → stderr log + exit code ≠0 (CI self-verification).\n if (res.expectFailures?.length) {\n for (const f of res.expectFailures) process.stderr.write(`flatc: ✗ ${f}\\n`)\n return 1\n }\n return 0\n}\n\n/** Parses `--at`: \"k=v[,k2=v2]\" → variable table (state override for rendering). */\nfunction parseVars(spec: string, into: Record<string, number>): void {\n for (const pair of spec.split(',')) {\n const i = pair.indexOf('=')\n if (i < 0) continue\n const k = pair.slice(0, i).trim(); const v = Number(pair.slice(i + 1).trim())\n if (k && Number.isFinite(v)) into[k] = v\n }\n}\n\n/** `--scale auto`: pick a resolution factor from the canvas size — enlarge small/thin content (so fine\n * filaments stay legible) while leaving large content at 1×. `max(160/long, 48/short)`, clamped [1, 8]. */\nfunction autoScale(w: number, h: number): number {\n const maxSide = Math.max(1, w, h), minSide = Math.max(1, Math.min(w, h))\n return Math.max(1, Math.min(8, Math.round(Math.max(160 / maxSide, 48 / minSide))))\n}\n\n/** Renders a Doc to a PNG file (headless skia). Async (SVG decode + raster). Shared by --render and --preview. */\nasync function renderDocToFile(doc: Doc, outPath: string, frame: number, vars: Record<string, number>, scale: number, steps: number, params: Record<string, string> = {}): Promise<number> {\n try {\n const { renderDocToPng } = await import('./render')\n const png = await renderDocToPng(doc, { frame, vars: Object.keys(vars).length ? vars : undefined, scale, steps: steps || undefined, params: Object.keys(params).length ? params : undefined })\n writeFileSync(outPath, png)\n } catch (e) {\n process.stderr.write(`flatc: render failed: ${(e as Error).message}\\n`)\n if (process.env.FLATC_DEBUG && (e as Error).stack) process.stderr.write(`${(e as Error).stack}\\n`) // FLATC_DEBUG=1 → full stack for diagnosis\n return 1\n }\n process.stdout.write(`flatc: ${basename(outPath)} ✓ ${doc.width}×${doc.height} ×${scale}${frame ? ` · frame ${frame}` : ''}${steps ? ` · ${steps} step(s)` : ''}${Object.keys(vars).length ? ` · ${Object.entries(vars).map(([k, v]) => `${k}=${v}`).join(' ')}` : ''}\\n`)\n return 0\n}\n\n/** --render: renders the file to PNG (headless skia). Async (SVG decode + raster). */\nasync function renderOnce(filePath: string, out: string, frame: number, vars: Record<string, number>, scale: number, steps: number, scaleAuto: boolean, noLibs = false, params: Record<string, string> = {}, explicitFlats: string[] = [], scriptPath = '', settle?: number): Promise<number> {\n let doc: Doc\n // `--no-libs` was parsed and then not passed on, so `--render` still auto-discovered the neighbouring\n // .flat files -- and failed on one, advising the very flag that had been given. Same hole in `--play`.\n try { doc = loadDoc(filePath, noLibs, explicitFlats) } catch (e) { process.stderr.write(`flatc: cannot read: ${(e as Error).message}\\n`); return 1 }\n // A frame past the end WRAPS silently, so a render can show frame 20 while the caller asked for 200\n // and reads the result as a renderer bug. It cost exactly that once.\n const dur = doc.timeline?.durationFrames ?? 0\n if (dur > 0 && frame >= dur) process.stderr.write(`flatc: --frame ${frame} is past the timeline (durationFrames ${dur}); the playhead WRAPS, so this renders frame ${frame % dur}\\n`)\n const outPath = out ? resolve(out) : join(dirname(filePath), basename(filePath, extname(filePath)) + '.png')\n const sc = scaleAuto ? autoScale(doc.width, doc.height) : scale\n if (!scriptPath) return renderDocToFile(doc, outPath, frame, vars, sc, steps, params)\n return renderAfterScript(doc, outPath, scriptPath, frame, vars, sc, steps, params, settle)\n}\n\n/**\n * `--render --script`: replays a gesture script (as `--play` does) and writes the image of the scene it\n * leaves. A `{\"type\":\"shot\",\"name\":\"…\"}` gesture writes the image AT THAT POINT, next to the output\n * (`out.<name>.png`, or `out.<n>.png` unnamed). The output itself is always the final state. A failed\n * `expect` fails the run, images written all the same: they are what one wants to look at then.\n */\nasync function renderAfterScript(doc: Doc, outPath: string, scriptPath: string, frame: number, vars: Record<string, number>, scale: number, steps: number, params: Record<string, string>, settle?: number): Promise<number> {\n if (!existsSync(scriptPath)) { process.stderr.write(`flatc: script not found: ${scriptPath}\\n`); return 1 }\n let gestures: Gesture[]\n try { gestures = JSON.parse(readFileSync(scriptPath, 'utf8')) as Gesture[] } catch (e) { process.stderr.write(`flatc: invalid JSON script: ${(e as Error).message}\\n`); return 1 }\n if (!Array.isArray(gestures)) { process.stderr.write('flatc: the script must be an array of gestures\\n'); return 1 }\n const shotPath = (name: string) => join(dirname(outPath), `${basename(outPath, extname(outPath))}.${name.replace(/[^\\w.-]/g, '_')}${extname(outPath) || '.png'}`)\n const written: string[] = []\n let failures: string[] = [], warned: string[] = []\n try {\n const { createRenderer } = await import('./render')\n const r = await createRenderer(doc, { scale, params: Object.keys(params).length ? params : undefined, interactive: true, settle })\n try {\n // The starting point is the one `--render` would draw: the frame, then the `--at` values.\n await r.frame(frame, { vars: Object.keys(vars).length ? vars : undefined })\n let shots = 0\n for (const g of gestures) {\n if (g.type === 'shot') { const p = shotPath(g.name || String(++shots)); writeFileSync(p, await r.capture()); written.push(basename(p)); continue }\n const rep = r.play([g])\n failures = rep.expectFailures; warned = rep.warnings\n }\n if (steps > 0) r.play([{ type: 'wait', frames: Math.floor(steps) }])\n writeFileSync(outPath, await r.capture())\n } finally { r.close() }\n } catch (e) {\n process.stderr.write(`flatc: render failed: ${(e as Error).message}\\n`)\n return 1\n }\n const played = gestures.filter((g) => g.type !== 'shot').length\n process.stdout.write(`flatc: ${basename(outPath)} ✓ ${doc.width}×${doc.height} ×${scale} · after ${played} gesture(s)${steps ? ` · ${steps} step(s)` : ''}${written.length ? ` · shots: ${written.join(', ')}` : ''}\\n`)\n for (const w of warned) process.stderr.write(`flatc: warning: ${w}\\n`)\n for (const f of failures) process.stderr.write(`flatc: ✗ ${f}\\n`)\n return failures.length ? 1 : 0\n}\n\nconst gcd = (a: number, b: number): number => { a = Math.abs(a); b = Math.abs(b); while (b) { const t = a % b; a = b; b = t } return a || 1 }\nconst lcm = (a: number, b: number): number => Math.max(1, Math.round((a / gcd(a, b)) * b))\n\n/** Every instance in a symbol's subtree (descends into local groups; does NOT cross into other symbols). */\nfunction* instancesOf(sym: SymbolDef): Generator<Instance> {\n const stack: Item[] = []\n for (const l of sym.layers) stack.push(...l.items)\n for (let it = stack.pop(); it; it = stack.pop()) {\n if (isInstance(it)) yield it\n else if (isGroup(it)) for (const l of it.layers) stack.push(...l.items)\n }\n}\n\n/**\n * Preview window length (frames). A `synced` instance loops on the PARENT's frame, so the symbol's own\n * duration suffices — that's the historical behavior, kept for every symbol with no MovieClip descendant.\n * But an `independent` (`loop`) descendant runs on its OWN duration, immune to the parent's wrap: to SHOW it\n * loop CLEANLY the window must be a common multiple of every such clip's duration (and the root's) — its\n * \"loop seam\". `once` clips only need the window to REACH their last frame (they hold afterwards). We walk\n * the subtree, fold each independent clip's duration into the seam (LCM), and extend past the longest clip,\n * keeping the window a whole number of seams. Capped, so coprime durations can't blow the window up.\n */\nfunction previewDuration(root: SymbolDef, symbols: SymbolDef[]): number {\n const byId = new Map(symbols.map((s) => [s.id, s]))\n const seen = new Set<string>()\n let seam = Math.max(1, root.timeline?.durationFrames ?? 1) // clean-loop period (multiple of every loop clip)\n let longest = seam // window must reach the last frame of any clip\n const walk = (sym: SymbolDef | undefined): void => {\n if (!sym || seen.has(sym.id)) return\n seen.add(sym.id)\n for (const it of instancesOf(sym)) {\n const child = byId.get(it.symbolId)\n const cdur = Math.max(1, child?.timeline?.durationFrames ?? 1)\n const mode = it.playback?.mode\n if (mode === 'independent') { seam = lcm(seam, cdur); longest = Math.max(longest, cdur) }\n else if (mode === 'once') longest = Math.max(longest, cdur)\n walk(child)\n }\n }\n walk(root)\n let dur = seam\n while (dur < longest) dur += seam // keep a clean loop seam while covering the longest MovieClip\n return Math.min(dur, 6000) // hard cap (~4 min @24fps) against a pathological coprime-duration blow-up\n}\n\n/**\n * Wraps ONE symbol from a `.flat` library into a minimal, playable Doc: a single centered instance on a\n * canvas auto-sized to the symbol's frame-0 bounds (+ padding). Lets you preview a library asset without\n * hand-authoring a wrapper `.flatink`. The root timeline borrows the symbol's own fps and a duration long\n * enough to show every nested MovieClip (`loop`/`once`) loop cleanly (`previewDuration`). Throws if the lib\n * is empty or the named symbol is missing.\n */\nfunction buildPreviewDoc(flatPath: string, symbolName: string, pad: number, bboxMode: 'all' | 'frame0', setSpec: Record<string, string> = {}): { doc: Doc; symbol: SymbolDef; others: string[] } {\n const { symbols } = parseFlatLib(readFileSync(flatPath, 'utf8'))\n if (!symbols.length) throw new Error('no symbols found in this .flat library')\n let symbol = symbols[0]\n if (symbolName) {\n const found = symbols.find((s) => s.name === symbolName)\n if (!found) throw new Error(`symbol \"${symbolName}\" not found — available: ${symbols.map((s) => s.name).join(', ')}`)\n symbol = found\n }\n\n const instance: Instance = { id: 'preview_instance', kind: 'instance', name: symbol.name, transform: { ...IDENTITY }, symbolId: symbol.id }\n const layer: Layer = { id: 'preview_layer', name: 'preview', visible: true, locked: false, opacity: 1, items: [instance] }\n const tl = symbol.timeline\n const dur = previewDuration(symbol, symbols)\n const timeline = { fps: tl?.fps ?? 24, durationFrames: dur, tracks: [] }\n // ALL symbols stay in the library (the chosen one may instance the others) — only `instance` is on stage.\n let doc: Doc = { width: 1, height: 1, layers: [layer], symbols, timeline }\n\n // `--set param=value`: set the preview instance's exposed params (call-site values, raw literals). The\n // renderer resolves them per the symbol's ParamDef / state machines (color → fill, number/bool → scope,\n // state name → driven frame). A light validation warns on names the symbol does not expose.\n const entries = Object.entries(setSpec)\n if (entries.length) {\n const known = new Set([...(symbol.states ?? []).map((s) => s.param), ...(symbol.params ?? []).map((p) => p.name)])\n for (const [k, v] of entries) {\n if (!known.has(k)) process.stderr.write(`flatc: --set ${k}: \"${symbol.name}\" exposes no such param (${[...known].join(', ') || 'none'})\\n`)\n else (instance.params ??= {})[k] = v\n }\n }\n // Auto-size & center. Default `--bbox all`: UNION over every frame of the symbol (sub-timelines NOT\n // frozen) so motion that drifts/rotates/grows is never clipped. `--bbox frame0` keeps the old frame-0\n // measure (+`--pad` to absorb motion).\n // Sample at most ~240 evenly-spaced frames (+ the last) so a long/abusive `durationFrames` can't blow\n // up memory/time; a sparse union is virtually as tight as every-frame for real motion.\n const sampleFrames = (n: number): number[] => {\n const step = Math.max(1, Math.ceil(n / 240))\n const fs: number[] = []\n for (let f = 0; f < n; f += step) fs.push(f)\n if (fs[fs.length - 1] !== n - 1) fs.push(n - 1)\n return fs\n }\n // Measured AS DRAWN (`scoped`): the symbol's `expr` channels evaluated with its params — the declared\n // defaults and the `--set` values, which is why they are applied first. A bar stretched by\n // `expr scaleX \"long\"` used to be framed on its base shape, and came out cropped.\n const bb = bboxMode === 'all' ? containerBBoxUnion(doc, instance, sampleFrames(dur), instance.transform, true) : containerBBox(doc, instance, 0, instance.transform, false, true)\n if (bb) {\n doc = { ...doc, width: Math.max(1, Math.ceil(bb.maxX - bb.minX) + pad * 2), height: Math.max(1, Math.ceil(bb.maxY - bb.minY) + pad * 2) }\n instance.transform = { ...IDENTITY, e: pad - bb.minX, f: pad - bb.minY }\n } else {\n doc = { ...doc, width: 512, height: 512 } // empty/degenerate symbol: fall back to a fixed centered stage\n instance.transform = { ...IDENTITY, e: 256, f: 256 }\n }\n\n return { doc, symbol, others: symbols.filter((s) => s !== symbol).map((s) => s.name) }\n}\n\n/** --preview: wraps one symbol of a `.flat` into a playable Doc, then writes a .flatpack (default) or a PNG (with --render). */\nasync function previewOnce(flatPath: string, symbolName: string, out: string, frame: number, vars: Record<string, number>, scale: number, steps: number, doRender: boolean, pad: number, bboxMode: 'all' | 'frame0', setSpec: Record<string, string>, scaleAuto: boolean): Promise<number> {\n let built: { doc: Doc; symbol: SymbolDef; others: string[] }\n try { built = buildPreviewDoc(flatPath, symbolName, pad, bboxMode, setSpec) }\n catch (e) { process.stderr.write(`flatc: ${(e as Error).message}\\n`); return 1 }\n const { doc, symbol, others } = built\n // A symbol with `states` takes its pose from the STATE, not from the playhead: `--frame` then only\n // advances the loops nested in it, and two frames come out identical without a word.\n if (frame > 0 && symbol.states?.length) {\n const sm = symbol.states[0]\n process.stderr.write(`flatc: --frame ${frame} does not move \"${symbol.name}\": its pose is set by its states (\\`${sm.param}\\`), not by the playhead. To see a state, or a point between two: --set ${sm.param}=<${sm.states.map((x) => x.name).join(' | ')} | a value, e.g. ${sm.param}=0.5>\\n`)\n }\n const stem = `${basename(flatPath, extname(flatPath))}.${symbol.name}`\n let code: number\n if (doRender) {\n // auto scale from the CONTENT size (minus the padding), so thin filaments still trigger an upscale.\n const sc = scaleAuto ? autoScale(doc.width - 2 * pad, doc.height - 2 * pad) : scale\n code = await renderDocToFile(doc, out ? resolve(out) : join(dirname(flatPath), stem + '.png'), frame, vars, sc, steps)\n } else {\n const outPath = out ? resolve(out) : join(dirname(flatPath), stem + '.flatpack')\n writeFileSync(outPath, packToJSON(doc))\n process.stdout.write(`flatc: ${basename(outPath)} ✓ preview of \"${symbol.name}\" · ${doc.width}×${doc.height} · play it in the browser player\\n`)\n code = 0\n }\n if (!symbolName && others.length) process.stderr.write(`flatc: previewed \"${symbol.name}\"; other symbols in this lib: ${others.join(', ')} (pick with --symbol <name>)\\n`)\n return code\n}\n\n/**\n * `--since <version>`: what these sources draw DIFFERENTLY from that version, for an author deciding what\n * to do about it once, when upgrading. Read from the SOURCE, so every finding has its line.\n *\n * One change is recorded: until 0.36 a path made only of lines was rounded at its gentle turns; it is now\n * drawn straight. Each such path is listed with how far its outline moves — a soft hill written as four\n * points wants `smooth` back, a hexagon does not. This is a report, not a warning of `--check`: a polyline\n * that is MEANT straight has nothing to add to say so, and would be warned about for ever.\n */\nconst SINCE_OPEN_UNITS = 2 // an open line moving by this much is reported whatever its length\n\nfunction sinceReport(files: string[], since: string, all = false): number {\n const [maj, min] = since.split('.').map(Number)\n if (!Number.isFinite(maj) || !Number.isFinite(min)) { process.stderr.write(`flatc: --since takes a version, e.g. --since 0.35 (got \"${since}\")\\n`); return 1 }\n if (maj > 0 || min >= 36) { process.stdout.write(`flatc: nothing is drawn differently since ${since}\\n`); return 0 }\n let open = 0, closed = 0, slight = 0\n for (const file of files) {\n let src: string\n try { src = readFileSync(file, 'utf8') } catch (e) { process.stderr.write(`flatc: cannot read ${file}: ${(e as Error).message}\\n`); return 1 }\n // Every `path \"…\"` of the file that does not already say `smooth` (a text laid `along path \"…\"` is not a shape).\n for (const m of src.matchAll(/(?<!along\\s)\\bpath\\s+\"([^\"]*)\"(?!\\s+smooth\\b)/g)) {\n // Read as it was BEFORE (free-hand material) to measure what smoothing did to it.\n for (const sub of parsePathData(m[1], { smooth: true }).subpaths) {\n if (sub.segments.some((sg) => sg.inHandle || sg.outHandle) || !softVertexCount(sub)) continue // has a curve, or only sharp corners\n const moved = smoothingDeviation(sub)\n const xs = sub.segments.map((sg) => sg.anchor.x), ys = sub.segments.map((sg) => sg.anchor.y)\n const size = Math.hypot(Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys))\n // Listed when the outline moves by 1% of the shape's size or more — RELATIVE, because artwork is often\n // drawn in a large space and scaled down by its container. An OPEN line is also listed from 2 units:\n // a long stroke with gentle turns (a hill, a horizon) moves by little of its length and visibly all\n // the same. Below that the two renderings are a few pixels apart at most; `--all` lists those too.\n if (moved <= 0 || size <= 0) continue\n if (moved < size * 0.01 && (sub.closed || moved < SINCE_OPEN_UNITS) && !all) { slight++; continue }\n const line = src.slice(0, m.index).split('\\n').length\n if (sub.closed) closed++; else open++\n process.stdout.write(`${file}:${line}: ${sub.closed ? 'closed' : 'open'} path of ${sub.segments.length} points was rounded before 0.36 and is now straight — it moves by up to ${Math.round(moved * 10) / 10} units (${Math.round((1000 * moved) / (size || 1)) / 10}% of its size). Write \\`smooth\\` after its path data to keep the curve.\\n`)\n }\n }\n }\n const n = open + closed\n const rest = slight ? ` ${slight} more move by less than 1% of their size, and by less than ${SINCE_OPEN_UNITS} units if open (\\`--all\\` lists them).` : ''\n process.stdout.write(n\n ? `flatc: ${n} path(s) of lines drawn differently since ${since}: ${open} open, ${closed} closed.${rest} Nothing was modified. (Rounded rectangles and paths mixing lines with curves changed too — their straight sides no longer bulge — and are not listed.)\\n`\n : `flatc: no path of lines is drawn visibly differently since ${since} in ${files.length} file(s).${rest}\\n`)\n return 0\n}\n\nexport function run(argv: string[]): number | Promise<number> {\n const args = argv.slice(2)\n let out = '', scriptPath = '', symbolName = ''\n let checkOnly = false, doFix = false, doWatch = false, doPlay = false, doRender = false, doTrace = false, doPreview = false, noLibs = false\n let frame = 0, scale = 2, steps = 0, pad = 24\n let scaleAuto = false\n let seed: number | undefined\n let settle: number | undefined\n let since = ''\n let bboxMode: 'all' | 'frame0' = 'all'\n let assetMode: AssetMode = 'inline'\n const vars: Record<string, number> = {}\n const setSpec: Record<string, string> = {} // `--set param=value` (state name or number) for --preview\n const positional: string[] = []\n for (let i = 0; i < args.length; i++) {\n const a = args[i]\n if (a === '-o' || a === '--out') out = args[++i] ?? ''\n else if (a === '--script') scriptPath = resolve(args[++i] ?? '')\n else if (a === '--assets') assetMode = args[++i] === 'external' ? 'external' : 'inline'\n else if (a === '--check') checkOnly = true\n else if (a === '--fix') { doFix = true; checkOnly = true }\n else if (a === '--no-libs') noLibs = true\n else if (a === '--watch') doWatch = true\n else if (a === '--play') doPlay = true\n else if (a === '--trace') doTrace = true\n else if (a === '--render') doRender = true\n else if (a === '--preview') doPreview = true\n else if (a === '--symbol') symbolName = args[++i] ?? ''\n else if (a === '--pad') pad = Math.max(0, Number(args[++i] ?? '24') || 0)\n else if (a === '--bbox') bboxMode = args[++i] === 'frame0' ? 'frame0' : 'all'\n else if (a === '--frame') frame = Number(args[++i] ?? '0') || 0\n else if (a === '--since') since = args[++i] ?? ''\n else if (a === '--all') { /* read by --since */ }\n else if (a === '--seed') { const v = Number(args[++i]); if (Number.isFinite(v)) seed = v }\n else if (a === '--settle') { const v = Number(args[++i]); if (Number.isFinite(v) && v >= 0) settle = Math.floor(v) }\n else if (a === '--steps') steps = Math.max(0, Number(args[++i] ?? '0') || 0)\n else if (a === '--scale') { const v = args[++i] ?? '2'; if (v === 'auto') scaleAuto = true; else scale = Number(v) || 2 }\n else if (a === '--at') parseVars(args[++i] ?? '', vars)\n else if (a === '--set') for (const pair of (args[++i] ?? '').split(',')) { const j = pair.indexOf('='); if (j > 0) setSpec[pair.slice(0, j).trim()] = pair.slice(j + 1).trim() }\n else if (a === '-h' || a === '--help') { printHelp(); return 0 }\n else positional.push(a)\n }\n if (!positional.length) { printHelp(); return 1 }\n\n const filePath = resolve(positional[0])\n if (!existsSync(filePath)) { process.stderr.write(`flatc: not found: ${filePath}\\n`); return 1 }\n\n if (since) return sinceReport(positional.map((f) => resolve(f)), since, args.includes('--all'))\n const explicitFlats = positional.slice(1)\n if (doPreview) return previewOnce(filePath, symbolName, out, frame, vars, scale, steps, doRender, pad, bboxMode, setSpec, scaleAuto)\n if (doRender) return renderOnce(filePath, out, frame, vars, scale, steps, scaleAuto, noLibs, setSpec, explicitFlats, scriptPath, settle)\n if (doPlay) return playOnce(filePath, scriptPath, doTrace, noLibs, explicitFlats, seed, settle)\n // `--check <library>.flat`: a `.flat` first positional is an asset LIB, not a program → lint via parseFlatLib\n // (the following positionals are more `.flat` libs to merge). Every other path is unchanged.\n const action: () => number = checkOnly && filePath.endsWith('.flat')\n ? () => checkFlatLibs([filePath, ...explicitFlats])\n : () => compileOnce(filePath, explicitFlats, out, checkOnly, assetMode, noLibs, doFix)\n if (doWatch) {\n const code = action()\n const baseDir = dirname(filePath)\n let timer: ReturnType<typeof setTimeout> | undefined\n // We ignore changes to the OUTPUT (.flatpack) — otherwise writing it would re-trigger the compile in a loop.\n watch(baseDir, { recursive: false }, (_e, filename) => {\n if (filename && (filename.endsWith('.flatpack') || filename.endsWith('.flatpack.json'))) return\n clearTimeout(timer); timer = setTimeout(action, 80)\n })\n process.stdout.write(`flatc: watching ${baseDir} … (Ctrl+C to stop)\\n`)\n return code\n }\n return action()\n}\n"],"mappings":"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","names":["f"]}
@@ -1,3 +1,5 @@
1
+ import { SendEvent } from '@flatkit/player';
2
+ import { Gesture } from '@flatkit/player/debug';
1
3
  import { Doc } from '@flatkit/types';
2
4
 
3
5
  type RenderOpts = {
@@ -6,6 +8,14 @@ type RenderOpts = {
6
8
  scale?: number;
7
9
  steps?: number;
8
10
  params?: Record<string, string>;
11
+ script?: Gesture[];
12
+ settle?: number;
13
+ };
14
+ /** What a replayed script reports: the `send`s it emitted, and the `expect` gestures that did not hold. */
15
+ type PlayReport = {
16
+ sends: SendEvent[];
17
+ expectFailures: string[];
18
+ warnings: string[];
9
19
  };
10
20
  /**
11
21
  * A renderer held OPEN over a document: the expensive setup is paid once, then any number of frames.
@@ -18,11 +28,19 @@ type RenderOpts = {
18
28
  * shims — including that `document` must exist or every `filter` and `tint` is dropped in SILENCE.
19
29
  */
20
30
  type Renderer = {
21
- /** PNG of one frame. `vars` overrides state for this frame; `steps` runs N sim steps before capture. */
31
+ /** PNG of one frame. `vars` overrides state for this frame; `script` replays gestures after the seek
32
+ * (an `interactive` renderer only); `steps` then runs N sim steps before capture. */
22
33
  frame(frame: number, opts?: {
23
34
  vars?: Record<string, number>;
24
35
  steps?: number;
36
+ script?: Gesture[];
25
37
  }): Promise<Uint8Array>;
38
+ /** Replays gestures on the live scene, as `flatc --play` does, and leaves it in the state they reach.
39
+ * Successive calls continue the same session (one state, one `expect` window). Needs
40
+ * `createRenderer(doc, { interactive: true })`. */
41
+ play(gestures: Gesture[]): PlayReport;
42
+ /** PNG of the scene AS IT IS NOW — after a `play`, without seeking. */
43
+ capture(): Promise<Uint8Array>;
26
44
  /** The document's pixel size at the renderer's scale. */
27
45
  readonly width: number;
28
46
  readonly height: number;
@@ -38,9 +56,11 @@ type Renderer = {
38
56
  declare function createRenderer(doc: Doc, opts?: {
39
57
  scale?: number;
40
58
  params?: Record<string, string>;
59
+ interactive?: boolean;
60
+ settle?: number;
41
61
  }): Promise<Renderer>;
42
62
  /** Render `doc` to a PNG (Buffer). `frame` = target image; `vars` = state override; `scale` = x-px (default 2).
43
63
  * A one-shot convenience over `createRenderer` — reach for the renderer itself as soon as you want two. */
44
64
  declare function renderDocToPng(doc: Doc, opts?: RenderOpts): Promise<Uint8Array>;
45
65
 
46
- export { type RenderOpts, type Renderer, createRenderer, renderDocToPng };
66
+ export { type PlayReport, type RenderOpts, type Renderer, createRenderer, renderDocToPng };
@@ -1,5 +1,6 @@
1
1
  // src/cli/render.ts
2
2
  import { FlatPlayer } from "@flatkit/player";
3
+ import { createReplayer } from "@flatkit/player/debug";
3
4
  import { isGroup, isInstance } from "@flatkit/engine/layers";
4
5
  import { mkdtempSync, writeFileSync, rmSync } from "fs";
5
6
  import { tmpdir } from "os";
@@ -81,17 +82,34 @@ async function createRenderer(doc, opts = {}) {
81
82
  set("document", { createElement: (t) => t === "canvas" ? new Canvas(1, 1) : {} });
82
83
  const pxW = Math.max(1, Math.round(W * scale));
83
84
  const pxH = Math.max(1, Math.round(H * scale));
85
+ const handlers = {};
84
86
  const canvas = new Canvas(pxW, pxH);
85
87
  const el = canvas;
86
88
  Object.assign(el, {
87
89
  getBoundingClientRect: () => ({ width: W, height: H, left: 0, top: 0, right: W, bottom: H }),
88
- addEventListener: () => {
90
+ // The listeners the player registers are KEPT: a replayed script fires into them (`play`).
91
+ addEventListener: (type, fn) => {
92
+ handlers[type] = fn;
89
93
  },
90
- removeEventListener: () => {
94
+ removeEventListener: (type) => {
95
+ delete handlers[type];
96
+ },
97
+ setPointerCapture: () => {
98
+ },
99
+ releasePointerCapture: () => {
91
100
  },
92
101
  style: {}
93
102
  });
94
- let player = new FlatPlayer(el, withParams, { input: false, audio: false, padding: 0, seed: 1, image: (id) => images.get(id) ?? null });
103
+ const sends = [];
104
+ let player = new FlatPlayer(el, withParams, { input: !!opts.interactive, audio: false, padding: 0, seed: 1, image: (id) => images.get(id) ?? null, onEvent: (e) => sends.push(e) });
105
+ let replayer = null;
106
+ const play = (gestures) => {
107
+ if (!player) throw new Error("renderer is closed");
108
+ if (!opts.interactive) throw new Error("this renderer cannot replay gestures: open it with createRenderer(doc, { interactive: true })");
109
+ replayer ??= createReplayer(player, withParams, handlers, sends, { settle: opts.settle });
110
+ for (const g2 of gestures) replayer.apply(g2);
111
+ return { sends: [...sends], expectFailures: [...replayer.expectFailures], warnings: [...replayer.warnings] };
112
+ };
95
113
  return {
96
114
  width: pxW,
97
115
  height: pxH,
@@ -99,6 +117,7 @@ async function createRenderer(doc, opts = {}) {
99
117
  if (!player) throw new Error("renderer is closed");
100
118
  if (frameOpts.vars) for (const [k, v] of Object.entries(frameOpts.vars)) player.setVar(k, v);
101
119
  player.seek(frame);
120
+ if (frameOpts.script?.length) play(frameOpts.script);
102
121
  const steps = frameOpts.steps ?? 0;
103
122
  if (steps > 0) {
104
123
  const n = Math.min(Math.floor(steps), MAX_RENDER_STEPS);
@@ -106,6 +125,13 @@ async function createRenderer(doc, opts = {}) {
106
125
  `);
107
126
  player.stepSim(n);
108
127
  }
128
+ player.render();
129
+ return el.toBuffer("png");
130
+ },
131
+ play,
132
+ async capture() {
133
+ if (!player) throw new Error("renderer is closed");
134
+ player.render();
109
135
  return el.toBuffer("png");
110
136
  },
111
137
  close() {
@@ -155,9 +181,9 @@ function applyParams(doc, params) {
155
181
  return out;
156
182
  }
157
183
  async function renderDocToPng(doc, opts = {}) {
158
- const renderer = await createRenderer(doc, { scale: opts.scale, params: opts.params });
184
+ const renderer = await createRenderer(doc, { scale: opts.scale, params: opts.params, interactive: !!opts.script?.length, settle: opts.settle });
159
185
  try {
160
- return await renderer.frame(opts.frame ?? 0, { vars: opts.vars, steps: opts.steps });
186
+ return await renderer.frame(opts.frame ?? 0, { vars: opts.vars, steps: opts.steps, script: opts.script });
161
187
  } finally {
162
188
  renderer.close();
163
189
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/render.ts"],"sourcesContent":["// -----------------------------------------------------------------------------\n// render.ts -- HEADLESS PNG rendering of a Doc (skia-canvas backend), for `flatc --render`.\n//\n// Gives agents/CI an IMAGE of what they author (the \"blind positioning\" safeguard). We replay the\n// real `FlatPlayer` on a skia canvas: same paths, gradients, filters (glow/shadow via `ctx.filter`)\n// and SVG as the browser. skia-canvas is loaded through a DYNAMIC import with a NON-LITERAL specifier,\n// so it is never part of the public build graph -- install it on demand only when `--render` is used.\n// -----------------------------------------------------------------------------\nimport { FlatPlayer } from '@flatkit/player'\nimport type { Doc, Item } from '@flatkit/types'\nimport { isGroup, isInstance } from '@flatkit/engine/layers'\nimport { mkdtempSync, writeFileSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\n\nexport type RenderOpts = { frame?: number; vars?: Record<string, number>; scale?: number; steps?: number; params?: Record<string, string> }\n\n/** Cap on `--steps` (anti-DoS: an untrusted doc must not freeze the render host). One step = 1/60 s of sim. */\nconst MAX_RENDER_STEPS = 10_000\n\n/** Minimal structural view of the `skia-canvas` surface we use. Kept local so the public build needs\n * no native binary; the real module is resolved at runtime only when `--render` runs. */\ninterface SkiaCanvas {\n Canvas: new (w: number, h: number) => HTMLCanvasElement & { toBuffer(fmt: string): Promise<Uint8Array> }\n loadImage: (src: string) => Promise<unknown>\n Path2D: typeof Path2D\n /** Browser global under Node: the mask/clip path builder does `new DOMMatrix([...])` (drawScene). */\n DOMMatrix: typeof DOMMatrix\n /** Global font registry. `use(paths)` reads each file's intrinsic name-table family; the 2-arg\n * `use(family, paths)` form FORCES that alias (fixes variable-font statics whose name table lies). */\n FontLibrary: {\n use(fontPaths: readonly string[]): Array<{ family: string }>\n use(family: string, fontPaths: readonly string[]): Array<{ family: string }>\n }\n}\n\n/** Map a font asset's MIME (or its data-URI) to a file extension skia-canvas can parse. */\nconst FONT_EXT: Record<string, string> = {\n 'font/woff2': '.woff2', 'font/woff': '.woff', 'font/ttf': '.ttf', 'font/otf': '.otf',\n 'font/collection': '.ttc', 'application/font-woff': '.woff', 'application/x-font-ttf': '.ttf',\n}\n\n/** Registers every embedded `font` asset with skia's FontLibrary so headless text uses the authored\n * faces (not host fallbacks). Fonts are baked as base64 data-URIs by flatc; FontLibrary only reads\n * FILE paths, so we materialize each into a temp dir, register it, and return that dir for cleanup. */\nfunction registerFonts(doc: Doc, FontLibrary: SkiaCanvas['FontLibrary']): { dir: string | null; families: string[] } {\n const fonts = (doc.assets ?? []).filter((a) => a.kind === 'font' && /^data:/.test(a.data ?? ''))\n if (fonts.length === 0) return { dir: null, families: [] }\n const dir = mkdtempSync(join(tmpdir(), 'flatc-fonts-'))\n const families: string[] = []\n for (const a of fonts) {\n const b64 = a.data.slice(a.data.indexOf(',') + 1)\n const ext = FONT_EXT[a.mime] ?? `.${(a.mime.split('/')[1] || 'ttf').replace(/[^\\w]/g, '')}`\n const file = join(dir, a.id.replace(/[^\\w.-]/g, '_') + ext)\n try {\n writeFileSync(file, Buffer.from(b64, 'base64'))\n // `family` alias (from `asset … font \"Name\"`) forces the registered name, so the face matches the\n // text's `font \"Name\"` even when the file's own name table is wrong (variable-font static export).\n const used = a.family ? FontLibrary.use(a.family, [file]) : FontLibrary.use([file])\n for (const f of used) if (!families.includes(f.family)) families.push(f.family)\n } catch (e) {\n process.stderr.write(`flatc: skipped font asset \"${a.id}\" (${a.mime}): ${(e as Error).message}\\n`)\n }\n }\n return { dir, families }\n}\n\n/**\n * A renderer held OPEN over a document: the expensive setup is paid once, then any number of frames.\n *\n * Every consumer that needed more than one image — a GIF, an MP4, a contact sheet, a loop check —\n * reimplemented this on top of the player, because the only thing on offer rendered a single frame and\n * paid the whole cost each time: the dynamic `skia-canvas` import, writing the embedded fonts to a temp\n * dir and registering them, decoding every image asset, building a `FlatPlayer`, installing ten globals.\n * Four such harnesses exist across two neighbouring repos, ~430 lines, each rediscovering the same\n * shims — including that `document` must exist or every `filter` and `tint` is dropped in SILENCE.\n */\nexport type Renderer = {\n /** PNG of one frame. `vars` overrides state for this frame; `steps` runs N sim steps before capture. */\n frame(frame: number, opts?: { vars?: Record<string, number>; steps?: number }): Promise<Uint8Array>\n /** The document's pixel size at the renderer's scale. */\n readonly width: number\n readonly height: number\n /** Restore the globals and drop the temp font files. Always call it — it is not optional. */\n close(): void\n}\n\n/**\n * Open a renderer over `doc`. The globals it installs (`document`, `Path2D`, `DOMMatrix`, …) stay in\n * place until `close()`, which is what makes a frame loop cheap; it also means one renderer at a time\n * per process. `params` sets a SYMBOL's exposed params (a state name or a number) before the first\n * frame — the reason a consumer had to write its own harness to preview anything with a state.\n */\nexport async function createRenderer(doc: Doc, opts: { scale?: number; params?: Record<string, string> } = {}): Promise<Renderer> {\n // Non-literal specifier: tsc does not resolve it, so skia-canvas is not a build dependency.\n const skiaPkg: string = 'skia-canvas'\n let skia: SkiaCanvas\n try { skia = (await import(skiaPkg)) as unknown as SkiaCanvas }\n catch {\n throw new Error(\n 'skia-canvas is required for rendering. Install it as a dev dependency: `npm i -D skia-canvas` ' +\n '(pnpm users: also allow its build script with `pnpm approve-builds` or add \"skia-canvas\" to ' +\n 'pnpm.onlyBuiltDependencies, then reinstall). If the native binary is still missing afterwards, ' +\n 'run `node node_modules/skia-canvas/lib/prebuild.mjs download`.',\n )\n }\n const { Canvas, loadImage, Path2D, FontLibrary, DOMMatrix } = skia\n const scale = opts.scale && opts.scale > 0 ? opts.scale : 2\n const W = doc.width, H = doc.height\n\n const withParams = opts.params ? applyParams(doc, opts.params) : doc\n\n // Register embedded fonts up front so the player's `ctx.font` resolves to the authored faces.\n const { dir: fontDir, families } = registerFonts(withParams, FontLibrary)\n if (families.length) process.stderr.write(`flatc: registered ${families.length} font famil${families.length > 1 ? 'ies' : 'y'}: ${families.join(', ')}\\n`)\n\n // Pre-decode the image assets (skia reads SVG/PNG/... from the data-URIs embedded by flatc).\n const images = new Map<string, CanvasImageSource>()\n for (const a of withParams.assets ?? []) {\n if (a.kind === 'svg' || a.kind === 'image' || /^data:image\\//.test(a.data ?? '')) {\n try { images.set(a.id, (await loadImage(a.data)) as unknown as CanvasImageSource) } catch { /* unreadable asset: ignored (placeholder) */ }\n }\n }\n\n // Globals the player/rendering expects under Node (restored by close()).\n const g = globalThis as Record<string, unknown>\n const saved: Record<string, unknown> = {}\n const set = (k: string, v: unknown) => { saved[k] = g[k]; g[k] = v }\n set('Path2D', Path2D)\n set('DOMMatrix', DOMMatrix) // mask/clip layers build their clip path with `new DOMMatrix([...])` (no browser global under Node)\n set('window', { addEventListener() {}, removeEventListener() {}, devicePixelRatio: scale })\n set('addEventListener', () => {})\n set('removeEventListener', () => {})\n set('requestAnimationFrame', () => 0)\n set('cancelAnimationFrame', () => {})\n set('document', { createElement: (t: string) => (t === 'canvas' ? new Canvas(1, 1) : {}) }) // off-screen canvas (filters/tint)\n\n const pxW = Math.max(1, Math.round(W * scale))\n const pxH = Math.max(1, Math.round(H * scale))\n const canvas = new Canvas(pxW, pxH)\n const el = canvas as unknown as HTMLCanvasElement & { toBuffer(fmt: string): Promise<Uint8Array> }\n Object.assign(el, {\n getBoundingClientRect: () => ({ width: W, height: H, left: 0, top: 0, right: W, bottom: H }),\n addEventListener: () => {}, removeEventListener: () => {}, style: {},\n })\n\n let player: FlatPlayer | null = new FlatPlayer(el, withParams, { input: false, audio: false, padding: 0, seed: 1, image: (id) => images.get(id) ?? null })\n\n return {\n width: pxW,\n height: pxH,\n async frame(frame, frameOpts = {}) {\n if (!player) throw new Error('renderer is closed')\n if (frameOpts.vars) for (const [k, v] of Object.entries(frameOpts.vars)) player.setVar(k, v)\n player.seek(frame) // applies frame + state\n const steps = frameOpts.steps ?? 0\n if (steps > 0) {\n const n = Math.min(Math.floor(steps), MAX_RENDER_STEPS)\n if (steps > MAX_RENDER_STEPS) process.stderr.write(`flatc: steps clamped to ${MAX_RENDER_STEPS} (was ${steps})\\n`)\n player.stepSim(n) // run N fixed sim steps (onEnterFrame) so a stateful act unfolds before capture\n }\n return el.toBuffer('png')\n },\n close() {\n player?.destroy()\n player = null\n for (const k in saved) { if (saved[k] === undefined) delete g[k]; else g[k] = saved[k] }\n // skia keeps the parsed faces in memory after `use()`, so the temp files are safe to drop now.\n if (fontDir) try { rmSync(fontDir, { recursive: true, force: true }) } catch { /* best-effort cleanup */ }\n },\n }\n}\n\n/**\n * A copy of `doc` with the scene's instances carrying `params` — a state NAME or a number, the same\n * spelling `--preview --set` accepts, resolved by the renderer per the symbol's ParamDef / state\n * machines. Rendering a program could only override document `var`s, so previewing a door in its `open`\n * state meant writing a harness that mutated the symbol by hand. That was one of four such harnesses.\n *\n * Set on the INSTANCE, not on the symbol's defaults: that is where a call-site value belongs, and it is\n * what `--preview` already does.\n */\nfunction applyParams(doc: Doc, params: Record<string, string>): Doc {\n const entries = Object.entries(params)\n if (!entries.length) return doc\n // Which names each symbol exposes: its typed params, plus the param each state machine drives.\n const exposed = new Map<string, Set<string>>()\n for (const sym of doc.symbols) {\n exposed.set(sym.id, new Set([...(sym.states ?? []).map((st) => st.param), ...(sym.params ?? []).map((p) => p.name)]))\n }\n const out = structuredClone(doc)\n const applied = new Set<string>()\n const visit = (items: Item[]): void => {\n for (const it of items) {\n if (isInstance(it)) {\n const known = exposed.get(it.symbolId)\n if (known) {\n for (const [k, v] of entries) {\n if (!known.has(k)) continue\n ;(it.params ??= {})[k] = v\n applied.add(k)\n }\n }\n }\n if (isGroup(it)) for (const l of it.layers) visit(l.items)\n }\n }\n for (const l of out.layers) visit(l.items)\n for (const sym of out.symbols) for (const l of sym.layers) visit(l.items)\n for (const [k] of entries) {\n if (!applied.has(k)) process.stderr.write(`flatc: no symbol exposes a param named \"${k}\"\\n`)\n }\n return out\n}\n\n/** Render `doc` to a PNG (Buffer). `frame` = target image; `vars` = state override; `scale` = x-px (default 2).\n * A one-shot convenience over `createRenderer` — reach for the renderer itself as soon as you want two. */\nexport async function renderDocToPng(doc: Doc, opts: RenderOpts = {}): Promise<Uint8Array> {\n const renderer = await createRenderer(doc, { scale: opts.scale, params: opts.params })\n try {\n return await renderer.frame(opts.frame ?? 0, { vars: opts.vars, steps: opts.steps })\n } finally {\n renderer.close()\n }\n}\n"],"mappings":";AAQA,SAAS,kBAAkB;AAE3B,SAAS,SAAS,kBAAkB;AACpC,SAAS,aAAa,eAAe,cAAc;AACnD,SAAS,cAAc;AACvB,SAAS,YAAY;AAKrB,IAAM,mBAAmB;AAmBzB,IAAM,WAAmC;AAAA,EACvC,cAAc;AAAA,EAAU,aAAa;AAAA,EAAS,YAAY;AAAA,EAAQ,YAAY;AAAA,EAC9E,mBAAmB;AAAA,EAAQ,yBAAyB;AAAA,EAAS,0BAA0B;AACzF;AAKA,SAAS,cAAc,KAAU,aAAoF;AACnH,QAAM,SAAS,IAAI,UAAU,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,SAAS,UAAU,SAAS,KAAK,EAAE,QAAQ,EAAE,CAAC;AAC/F,MAAI,MAAM,WAAW,EAAG,QAAO,EAAE,KAAK,MAAM,UAAU,CAAC,EAAE;AACzD,QAAM,MAAM,YAAY,KAAK,OAAO,GAAG,cAAc,CAAC;AACtD,QAAM,WAAqB,CAAC;AAC5B,aAAW,KAAK,OAAO;AACrB,UAAM,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,QAAQ,GAAG,IAAI,CAAC;AAChD,UAAM,MAAM,SAAS,EAAE,IAAI,KAAK,KAAK,EAAE,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK,OAAO,QAAQ,UAAU,EAAE,CAAC;AACzF,UAAM,OAAO,KAAK,KAAK,EAAE,GAAG,QAAQ,YAAY,GAAG,IAAI,GAAG;AAC1D,QAAI;AACF,oBAAc,MAAM,OAAO,KAAK,KAAK,QAAQ,CAAC;AAG9C,YAAM,OAAO,EAAE,SAAS,YAAY,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,YAAY,IAAI,CAAC,IAAI,CAAC;AAClF,iBAAW,KAAK,KAAM,KAAI,CAAC,SAAS,SAAS,EAAE,MAAM,EAAG,UAAS,KAAK,EAAE,MAAM;AAAA,IAChF,SAAS,GAAG;AACV,cAAQ,OAAO,MAAM,8BAA8B,EAAE,EAAE,MAAM,EAAE,IAAI,MAAO,EAAY,OAAO;AAAA,CAAI;AAAA,IACnG;AAAA,EACF;AACA,SAAO,EAAE,KAAK,SAAS;AACzB;AA4BA,eAAsB,eAAe,KAAU,OAA4D,CAAC,GAAsB;AAEhI,QAAM,UAAkB;AACxB,MAAI;AACJ,MAAI;AAAE,WAAQ,MAAM,OAAO;AAAA,EAAmC,QACxD;AACJ,UAAM,IAAI;AAAA,MACR;AAAA,IAIF;AAAA,EACF;AACA,QAAM,EAAE,QAAQ,WAAW,QAAQ,aAAa,UAAU,IAAI;AAC9D,QAAM,QAAQ,KAAK,SAAS,KAAK,QAAQ,IAAI,KAAK,QAAQ;AAC1D,QAAM,IAAI,IAAI,OAAO,IAAI,IAAI;AAE7B,QAAM,aAAa,KAAK,SAAS,YAAY,KAAK,KAAK,MAAM,IAAI;AAGjE,QAAM,EAAE,KAAK,SAAS,SAAS,IAAI,cAAc,YAAY,WAAW;AACxE,MAAI,SAAS,OAAQ,SAAQ,OAAO,MAAM,qBAAqB,SAAS,MAAM,cAAc,SAAS,SAAS,IAAI,QAAQ,GAAG,KAAK,SAAS,KAAK,IAAI,CAAC;AAAA,CAAI;AAGzJ,QAAM,SAAS,oBAAI,IAA+B;AAClD,aAAW,KAAK,WAAW,UAAU,CAAC,GAAG;AACvC,QAAI,EAAE,SAAS,SAAS,EAAE,SAAS,WAAW,gBAAgB,KAAK,EAAE,QAAQ,EAAE,GAAG;AAChF,UAAI;AAAE,eAAO,IAAI,EAAE,IAAK,MAAM,UAAU,EAAE,IAAI,CAAkC;AAAA,MAAE,QAAQ;AAAA,MAAgD;AAAA,IAC5I;AAAA,EACF;AAGA,QAAM,IAAI;AACV,QAAM,QAAiC,CAAC;AACxC,QAAM,MAAM,CAAC,GAAW,MAAe;AAAE,UAAM,CAAC,IAAI,EAAE,CAAC;AAAG,MAAE,CAAC,IAAI;AAAA,EAAE;AACnE,MAAI,UAAU,MAAM;AACpB,MAAI,aAAa,SAAS;AAC1B,MAAI,UAAU,EAAE,mBAAmB;AAAA,EAAC,GAAG,sBAAsB;AAAA,EAAC,GAAG,kBAAkB,MAAM,CAAC;AAC1F,MAAI,oBAAoB,MAAM;AAAA,EAAC,CAAC;AAChC,MAAI,uBAAuB,MAAM;AAAA,EAAC,CAAC;AACnC,MAAI,yBAAyB,MAAM,CAAC;AACpC,MAAI,wBAAwB,MAAM;AAAA,EAAC,CAAC;AACpC,MAAI,YAAY,EAAE,eAAe,CAAC,MAAe,MAAM,WAAW,IAAI,OAAO,GAAG,CAAC,IAAI,CAAC,EAAG,CAAC;AAE1F,QAAM,MAAM,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,KAAK,CAAC;AAC7C,QAAM,MAAM,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,KAAK,CAAC;AAC7C,QAAM,SAAS,IAAI,OAAO,KAAK,GAAG;AAClC,QAAM,KAAK;AACX,SAAO,OAAO,IAAI;AAAA,IAChB,uBAAuB,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,EAAE;AAAA,IAC1F,kBAAkB,MAAM;AAAA,IAAC;AAAA,IAAG,qBAAqB,MAAM;AAAA,IAAC;AAAA,IAAG,OAAO,CAAC;AAAA,EACrE,CAAC;AAED,MAAI,SAA4B,IAAI,WAAW,IAAI,YAAY,EAAE,OAAO,OAAO,OAAO,OAAO,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,OAAO,IAAI,EAAE,KAAK,KAAK,CAAC;AAEzJ,SAAO;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM,MAAM,OAAO,YAAY,CAAC,GAAG;AACjC,UAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,oBAAoB;AACjD,UAAI,UAAU,KAAM,YAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,UAAU,IAAI,EAAG,QAAO,OAAO,GAAG,CAAC;AAC3F,aAAO,KAAK,KAAK;AACjB,YAAM,QAAQ,UAAU,SAAS;AACjC,UAAI,QAAQ,GAAG;AACb,cAAM,IAAI,KAAK,IAAI,KAAK,MAAM,KAAK,GAAG,gBAAgB;AACtD,YAAI,QAAQ,iBAAkB,SAAQ,OAAO,MAAM,2BAA2B,gBAAgB,SAAS,KAAK;AAAA,CAAK;AACjH,eAAO,QAAQ,CAAC;AAAA,MAClB;AACA,aAAO,GAAG,SAAS,KAAK;AAAA,IAC1B;AAAA,IACA,QAAQ;AACN,cAAQ,QAAQ;AAChB,eAAS;AACT,iBAAW,KAAK,OAAO;AAAE,YAAI,MAAM,CAAC,MAAM,OAAW,QAAO,EAAE,CAAC;AAAA,YAAQ,GAAE,CAAC,IAAI,MAAM,CAAC;AAAA,MAAE;AAEvF,UAAI,QAAS,KAAI;AAAE,eAAO,SAAS,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,MAAE,QAAQ;AAAA,MAA4B;AAAA,IAC3G;AAAA,EACF;AACF;AAWA,SAAS,YAAY,KAAU,QAAqC;AAClE,QAAM,UAAU,OAAO,QAAQ,MAAM;AACrC,MAAI,CAAC,QAAQ,OAAQ,QAAO;AAE5B,QAAM,UAAU,oBAAI,IAAyB;AAC7C,aAAW,OAAO,IAAI,SAAS;AAC7B,YAAQ,IAAI,IAAI,IAAI,oBAAI,IAAI,CAAC,IAAI,IAAI,UAAU,CAAC,GAAG,IAAI,CAAC,OAAO,GAAG,KAAK,GAAG,IAAI,IAAI,UAAU,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;AAAA,EACtH;AACA,QAAM,MAAM,gBAAgB,GAAG;AAC/B,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,QAAQ,CAAC,UAAwB;AACrC,eAAW,MAAM,OAAO;AACtB,UAAI,WAAW,EAAE,GAAG;AAClB,cAAM,QAAQ,QAAQ,IAAI,GAAG,QAAQ;AACrC,YAAI,OAAO;AACT,qBAAW,CAAC,GAAG,CAAC,KAAK,SAAS;AAC5B,gBAAI,CAAC,MAAM,IAAI,CAAC,EAAG;AAClB,aAAC,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI;AACzB,oBAAQ,IAAI,CAAC;AAAA,UACf;AAAA,QACF;AAAA,MACF;AACA,UAAI,QAAQ,EAAE,EAAG,YAAW,KAAK,GAAG,OAAQ,OAAM,EAAE,KAAK;AAAA,IAC3D;AAAA,EACF;AACA,aAAW,KAAK,IAAI,OAAQ,OAAM,EAAE,KAAK;AACzC,aAAW,OAAO,IAAI,QAAS,YAAW,KAAK,IAAI,OAAQ,OAAM,EAAE,KAAK;AACxE,aAAW,CAAC,CAAC,KAAK,SAAS;AACzB,QAAI,CAAC,QAAQ,IAAI,CAAC,EAAG,SAAQ,OAAO,MAAM,2CAA2C,CAAC;AAAA,CAAK;AAAA,EAC7F;AACA,SAAO;AACT;AAIA,eAAsB,eAAe,KAAU,OAAmB,CAAC,GAAwB;AACzF,QAAM,WAAW,MAAM,eAAe,KAAK,EAAE,OAAO,KAAK,OAAO,QAAQ,KAAK,OAAO,CAAC;AACrF,MAAI;AACF,WAAO,MAAM,SAAS,MAAM,KAAK,SAAS,GAAG,EAAE,MAAM,KAAK,MAAM,OAAO,KAAK,MAAM,CAAC;AAAA,EACrF,UAAE;AACA,aAAS,MAAM;AAAA,EACjB;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/cli/render.ts"],"sourcesContent":["// -----------------------------------------------------------------------------\n// render.ts -- HEADLESS PNG rendering of a Doc (skia-canvas backend), for `flatc --render`.\n//\n// Gives agents/CI an IMAGE of what they author (the \"blind positioning\" safeguard). We replay the\n// real `FlatPlayer` on a skia canvas: same paths, gradients, filters (glow/shadow via `ctx.filter`)\n// and SVG as the browser. skia-canvas is loaded through a DYNAMIC import with a NON-LITERAL specifier,\n// so it is never part of the public build graph -- install it on demand only when `--render` is used.\n// -----------------------------------------------------------------------------\nimport { FlatPlayer, type SendEvent } from '@flatkit/player'\nimport { createReplayer, type Gesture, type Handlers, type Replayer } from '@flatkit/player/debug'\nimport type { Doc, Item } from '@flatkit/types'\nimport { isGroup, isInstance } from '@flatkit/engine/layers'\nimport { mkdtempSync, writeFileSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\n\nexport type RenderOpts = { frame?: number; vars?: Record<string, number>; scale?: number; steps?: number; params?: Record<string, string>; script?: Gesture[]; settle?: number }\n\n/** What a replayed script reports: the `send`s it emitted, and the `expect` gestures that did not hold. */\nexport type PlayReport = { sends: SendEvent[]; expectFailures: string[]; warnings: string[] }\n\n/** Cap on `--steps` (anti-DoS: an untrusted doc must not freeze the render host). One step = 1/60 s of sim. */\nconst MAX_RENDER_STEPS = 10_000\n\n/** Minimal structural view of the `skia-canvas` surface we use. Kept local so the public build needs\n * no native binary; the real module is resolved at runtime only when `--render` runs. */\ninterface SkiaCanvas {\n Canvas: new (w: number, h: number) => HTMLCanvasElement & { toBuffer(fmt: string): Promise<Uint8Array> }\n loadImage: (src: string) => Promise<unknown>\n Path2D: typeof Path2D\n /** Browser global under Node: the mask/clip path builder does `new DOMMatrix([...])` (drawScene). */\n DOMMatrix: typeof DOMMatrix\n /** Global font registry. `use(paths)` reads each file's intrinsic name-table family; the 2-arg\n * `use(family, paths)` form FORCES that alias (fixes variable-font statics whose name table lies). */\n FontLibrary: {\n use(fontPaths: readonly string[]): Array<{ family: string }>\n use(family: string, fontPaths: readonly string[]): Array<{ family: string }>\n }\n}\n\n/** Map a font asset's MIME (or its data-URI) to a file extension skia-canvas can parse. */\nconst FONT_EXT: Record<string, string> = {\n 'font/woff2': '.woff2', 'font/woff': '.woff', 'font/ttf': '.ttf', 'font/otf': '.otf',\n 'font/collection': '.ttc', 'application/font-woff': '.woff', 'application/x-font-ttf': '.ttf',\n}\n\n/** Registers every embedded `font` asset with skia's FontLibrary so headless text uses the authored\n * faces (not host fallbacks). Fonts are baked as base64 data-URIs by flatc; FontLibrary only reads\n * FILE paths, so we materialize each into a temp dir, register it, and return that dir for cleanup. */\nfunction registerFonts(doc: Doc, FontLibrary: SkiaCanvas['FontLibrary']): { dir: string | null; families: string[] } {\n const fonts = (doc.assets ?? []).filter((a) => a.kind === 'font' && /^data:/.test(a.data ?? ''))\n if (fonts.length === 0) return { dir: null, families: [] }\n const dir = mkdtempSync(join(tmpdir(), 'flatc-fonts-'))\n const families: string[] = []\n for (const a of fonts) {\n const b64 = a.data.slice(a.data.indexOf(',') + 1)\n const ext = FONT_EXT[a.mime] ?? `.${(a.mime.split('/')[1] || 'ttf').replace(/[^\\w]/g, '')}`\n const file = join(dir, a.id.replace(/[^\\w.-]/g, '_') + ext)\n try {\n writeFileSync(file, Buffer.from(b64, 'base64'))\n // `family` alias (from `asset … font \"Name\"`) forces the registered name, so the face matches the\n // text's `font \"Name\"` even when the file's own name table is wrong (variable-font static export).\n const used = a.family ? FontLibrary.use(a.family, [file]) : FontLibrary.use([file])\n for (const f of used) if (!families.includes(f.family)) families.push(f.family)\n } catch (e) {\n process.stderr.write(`flatc: skipped font asset \"${a.id}\" (${a.mime}): ${(e as Error).message}\\n`)\n }\n }\n return { dir, families }\n}\n\n/**\n * A renderer held OPEN over a document: the expensive setup is paid once, then any number of frames.\n *\n * Every consumer that needed more than one image — a GIF, an MP4, a contact sheet, a loop check —\n * reimplemented this on top of the player, because the only thing on offer rendered a single frame and\n * paid the whole cost each time: the dynamic `skia-canvas` import, writing the embedded fonts to a temp\n * dir and registering them, decoding every image asset, building a `FlatPlayer`, installing ten globals.\n * Four such harnesses exist across two neighbouring repos, ~430 lines, each rediscovering the same\n * shims — including that `document` must exist or every `filter` and `tint` is dropped in SILENCE.\n */\nexport type Renderer = {\n /** PNG of one frame. `vars` overrides state for this frame; `script` replays gestures after the seek\n * (an `interactive` renderer only); `steps` then runs N sim steps before capture. */\n frame(frame: number, opts?: { vars?: Record<string, number>; steps?: number; script?: Gesture[] }): Promise<Uint8Array>\n /** Replays gestures on the live scene, as `flatc --play` does, and leaves it in the state they reach.\n * Successive calls continue the same session (one state, one `expect` window). Needs\n * `createRenderer(doc, { interactive: true })`. */\n play(gestures: Gesture[]): PlayReport\n /** PNG of the scene AS IT IS NOW — after a `play`, without seeking. */\n capture(): Promise<Uint8Array>\n /** The document's pixel size at the renderer's scale. */\n readonly width: number\n readonly height: number\n /** Restore the globals and drop the temp font files. Always call it — it is not optional. */\n close(): void\n}\n\n/**\n * Open a renderer over `doc`. The globals it installs (`document`, `Path2D`, `DOMMatrix`, …) stay in\n * place until `close()`, which is what makes a frame loop cheap; it also means one renderer at a time\n * per process. `params` sets a SYMBOL's exposed params (a state name or a number) before the first\n * frame — the reason a consumer had to write its own harness to preview anything with a state.\n */\nexport async function createRenderer(doc: Doc, opts: { scale?: number; params?: Record<string, string>; interactive?: boolean; settle?: number } = {}): Promise<Renderer> {\n // Non-literal specifier: tsc does not resolve it, so skia-canvas is not a build dependency.\n const skiaPkg: string = 'skia-canvas'\n let skia: SkiaCanvas\n try { skia = (await import(skiaPkg)) as unknown as SkiaCanvas }\n catch {\n throw new Error(\n 'skia-canvas is required for rendering. Install it as a dev dependency: `npm i -D skia-canvas` ' +\n '(pnpm users: also allow its build script with `pnpm approve-builds` or add \"skia-canvas\" to ' +\n 'pnpm.onlyBuiltDependencies, then reinstall). If the native binary is still missing afterwards, ' +\n 'run `node node_modules/skia-canvas/lib/prebuild.mjs download`.',\n )\n }\n const { Canvas, loadImage, Path2D, FontLibrary, DOMMatrix } = skia\n const scale = opts.scale && opts.scale > 0 ? opts.scale : 2\n const W = doc.width, H = doc.height\n\n const withParams = opts.params ? applyParams(doc, opts.params) : doc\n\n // Register embedded fonts up front so the player's `ctx.font` resolves to the authored faces.\n const { dir: fontDir, families } = registerFonts(withParams, FontLibrary)\n if (families.length) process.stderr.write(`flatc: registered ${families.length} font famil${families.length > 1 ? 'ies' : 'y'}: ${families.join(', ')}\\n`)\n\n // Pre-decode the image assets (skia reads SVG/PNG/... from the data-URIs embedded by flatc).\n const images = new Map<string, CanvasImageSource>()\n for (const a of withParams.assets ?? []) {\n if (a.kind === 'svg' || a.kind === 'image' || /^data:image\\//.test(a.data ?? '')) {\n try { images.set(a.id, (await loadImage(a.data)) as unknown as CanvasImageSource) } catch { /* unreadable asset: ignored (placeholder) */ }\n }\n }\n\n // Globals the player/rendering expects under Node (restored by close()).\n const g = globalThis as Record<string, unknown>\n const saved: Record<string, unknown> = {}\n const set = (k: string, v: unknown) => { saved[k] = g[k]; g[k] = v }\n set('Path2D', Path2D)\n set('DOMMatrix', DOMMatrix) // mask/clip layers build their clip path with `new DOMMatrix([...])` (no browser global under Node)\n set('window', { addEventListener() {}, removeEventListener() {}, devicePixelRatio: scale })\n set('addEventListener', () => {})\n set('removeEventListener', () => {})\n set('requestAnimationFrame', () => 0)\n set('cancelAnimationFrame', () => {})\n set('document', { createElement: (t: string) => (t === 'canvas' ? new Canvas(1, 1) : {}) }) // off-screen canvas (filters/tint)\n\n const pxW = Math.max(1, Math.round(W * scale))\n const pxH = Math.max(1, Math.round(H * scale))\n const handlers: Handlers = {}\n const canvas = new Canvas(pxW, pxH)\n const el = canvas as unknown as HTMLCanvasElement & { toBuffer(fmt: string): Promise<Uint8Array> }\n Object.assign(el, {\n getBoundingClientRect: () => ({ width: W, height: H, left: 0, top: 0, right: W, bottom: H }),\n // The listeners the player registers are KEPT: a replayed script fires into them (`play`).\n addEventListener: (type: string, fn: Handlers[string]) => { handlers[type] = fn },\n removeEventListener: (type: string) => { delete handlers[type] },\n setPointerCapture: () => {}, releasePointerCapture: () => {}, style: {},\n })\n\n // `interactive`: the player listens to its canvas, so gestures can be replayed on it. Off by default —\n // a renderer that only draws frames registers nothing.\n const sends: SendEvent[] = []\n let player: FlatPlayer | null = new FlatPlayer(el, withParams, { input: !!opts.interactive, audio: false, padding: 0, seed: 1, image: (id) => images.get(id) ?? null, onEvent: (e) => sends.push(e) })\n let replayer: Replayer | null = null\n const play = (gestures: Gesture[]): PlayReport => {\n if (!player) throw new Error('renderer is closed')\n if (!opts.interactive) throw new Error('this renderer cannot replay gestures: open it with createRenderer(doc, { interactive: true })')\n replayer ??= createReplayer(player, withParams, handlers, sends, { settle: opts.settle })\n for (const g of gestures) replayer.apply(g)\n return { sends: [...sends], expectFailures: [...replayer.expectFailures], warnings: [...replayer.warnings] }\n }\n\n return {\n width: pxW,\n height: pxH,\n async frame(frame, frameOpts = {}) {\n if (!player) throw new Error('renderer is closed')\n if (frameOpts.vars) for (const [k, v] of Object.entries(frameOpts.vars)) player.setVar(k, v)\n player.seek(frame) // applies frame + state\n if (frameOpts.script?.length) play(frameOpts.script)\n const steps = frameOpts.steps ?? 0\n if (steps > 0) {\n const n = Math.min(Math.floor(steps), MAX_RENDER_STEPS)\n if (steps > MAX_RENDER_STEPS) process.stderr.write(`flatc: steps clamped to ${MAX_RENDER_STEPS} (was ${steps})\\n`)\n player.stepSim(n) // run N fixed sim steps (onEnterFrame) so a stateful act unfolds before capture\n }\n player.render() // a gesture or a step paints only what it changed: draw the frame that is asked for\n return el.toBuffer('png')\n },\n play,\n async capture() {\n if (!player) throw new Error('renderer is closed')\n player.render()\n return el.toBuffer('png')\n },\n close() {\n player?.destroy()\n player = null\n for (const k in saved) { if (saved[k] === undefined) delete g[k]; else g[k] = saved[k] }\n // skia keeps the parsed faces in memory after `use()`, so the temp files are safe to drop now.\n if (fontDir) try { rmSync(fontDir, { recursive: true, force: true }) } catch { /* best-effort cleanup */ }\n },\n }\n}\n\n/**\n * A copy of `doc` with the scene's instances carrying `params` — a state NAME or a number, the same\n * spelling `--preview --set` accepts, resolved by the renderer per the symbol's ParamDef / state\n * machines. Rendering a program could only override document `var`s, so previewing a door in its `open`\n * state meant writing a harness that mutated the symbol by hand. That was one of four such harnesses.\n *\n * Set on the INSTANCE, not on the symbol's defaults: that is where a call-site value belongs, and it is\n * what `--preview` already does.\n */\nfunction applyParams(doc: Doc, params: Record<string, string>): Doc {\n const entries = Object.entries(params)\n if (!entries.length) return doc\n // Which names each symbol exposes: its typed params, plus the param each state machine drives.\n const exposed = new Map<string, Set<string>>()\n for (const sym of doc.symbols) {\n exposed.set(sym.id, new Set([...(sym.states ?? []).map((st) => st.param), ...(sym.params ?? []).map((p) => p.name)]))\n }\n const out = structuredClone(doc)\n const applied = new Set<string>()\n const visit = (items: Item[]): void => {\n for (const it of items) {\n if (isInstance(it)) {\n const known = exposed.get(it.symbolId)\n if (known) {\n for (const [k, v] of entries) {\n if (!known.has(k)) continue\n ;(it.params ??= {})[k] = v\n applied.add(k)\n }\n }\n }\n if (isGroup(it)) for (const l of it.layers) visit(l.items)\n }\n }\n for (const l of out.layers) visit(l.items)\n for (const sym of out.symbols) for (const l of sym.layers) visit(l.items)\n for (const [k] of entries) {\n if (!applied.has(k)) process.stderr.write(`flatc: no symbol exposes a param named \"${k}\"\\n`)\n }\n return out\n}\n\n/** Render `doc` to a PNG (Buffer). `frame` = target image; `vars` = state override; `scale` = x-px (default 2).\n * A one-shot convenience over `createRenderer` — reach for the renderer itself as soon as you want two. */\nexport async function renderDocToPng(doc: Doc, opts: RenderOpts = {}): Promise<Uint8Array> {\n const renderer = await createRenderer(doc, { scale: opts.scale, params: opts.params, interactive: !!opts.script?.length, settle: opts.settle })\n try {\n return await renderer.frame(opts.frame ?? 0, { vars: opts.vars, steps: opts.steps, script: opts.script })\n } finally {\n renderer.close()\n }\n}\n"],"mappings":"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","names":["g"]}
package/dist/compile.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  compileFlatpack,
3
3
  packToJSON
4
- } from "./chunk-KXKX2RMX.js";
4
+ } from "./chunk-BPX2MOW2.js";
5
5
  export {
6
6
  compileFlatpack,
7
7
  packToJSON
package/dist/index.js CHANGED
@@ -22,11 +22,11 @@ import {
22
22
  scopeProgram,
23
23
  scopeRegions,
24
24
  splitScopeProgram
25
- } from "./chunk-43MGBTUK.js";
25
+ } from "./chunk-RQKXBHHM.js";
26
26
  import {
27
27
  compileFlatpack,
28
28
  packToJSON
29
- } from "./chunk-KXKX2RMX.js";
29
+ } from "./chunk-BPX2MOW2.js";
30
30
 
31
31
  // src/manifest.ts
32
32
  import { EXPR_CHANNELS as EXPR_CHANNELS2, OFFSET_CHANNELS } from "@flatkit/engine/timeline";
@@ -59,6 +59,16 @@
59
59
  - `ellipse <cx> <cy> <rx> <ry>`
60
60
  - `rect <x> <y> <w> <h>` · `… <r>` (uniform rounded corners) · `… <rx> <ry>`
61
61
  No more hand-computing the k = 0.5523·r Bezier constant.
62
+ - **A path of lines is drawn STRAIGHT (since 0.36).** `L`, `H` and `V` are flat segments, next to a curve
63
+ or on their own: a hexagon has six flat sides, and a hill written as `path "M-20 250 L220 218 L620 228
64
+ L980 222"` is a broken line. Before 0.36 such a path was rounded at every turn under 60°. For a curve
65
+ through the points, write **`smooth`** right after the path data: `path "M-20 250 L220 218 L620 228"
66
+ smooth nofill stroke #fff 3`. **There is no warning on a short path**: `flatc --check` reports a
67
+ missing `smooth` only on a run of 12 points or more whose outline would really move. When upgrading a
68
+ corpus, `flatc <files> --since 0.35` lists every path of lines that changed, with its `file:line`.
69
+ - **`polyline <xs> <ys> [count <n> | count "<expr>"] [closed]`** draws through two ARRAY variables, read
70
+ every frame (a trajectory, a curve being computed). It takes the arrays' **names**, its segments are
71
+ straight, and fewer than two points draw nothing.
62
72
  - **`filter` on a path**: accepted (`path "…" fill #000 filter glow 6 #fff`). No need to wrap
63
73
  in a `group` just to add a shadow/glow. (Also works on group/image/text.)
64
74
  - **`linear(angle, …)` gradient**: `0` = → (left to right), `90` = ↓ (top to bottom).
@@ -368,12 +378,17 @@ and composes with `self.x`/`self.y` etc. (same `self`).
368
378
  param = error.
369
379
  - ⚠️ Compile-time sugar (re-serialized as groups, like `def`/`repeat`). For shared
370
380
  BEHAVIOR, see `each` below.
381
+ - **A plain `symbol "X" { … }` may be written in the program too** (before or after the `scene`): it is
382
+ the very block a `.flat` holds — its own `timeline`, cels, `states`, `params` — instanced without
383
+ parens, and it wins over a library symbol of the same name. No dummy parameter needed any more.
384
+ - **`repeat` / `def` / `$()` work in a `.flat` library**, as in a program's scene: `repeat i from 0 to 8
385
+ { circle $(i*20) 0 6 fill #333 }` inside a symbol's layer is unfolded when the library is read.
371
386
  - ⚠️ **`.flatink`-only**: a parameterized `symbol "X"(…)` lives in the **program** (`.flatink`), NOT in a
372
387
  `.flat` library. `.flat` libs hold **non-parameterized** symbols, instanced **without** parens
373
388
  (`instance "Hero" as "H"`); a parameterized one is instanced **with** args (`instance "Card"("A")`).
374
389
  Putting a `(…)` symbol in a `.flat` — or letting `flatc` auto-discover such a `.flat` in the folder —
375
390
  surfaces as a misleading `"{" expected, "("` (the `.flat` parser doesn't take parameters). Rule of
376
- thumb: parens ⇔ parameterized ⇔ inline in the `.flatink`.
391
+ thumb: parens ⇔ parameterized ⇔ program only; no parens ⇔ a real symbol, in a `.flat` or in the program.
377
392
  - **`each "Symbol" as i { … }`**: applies BEHAVIOR to every instance of a symbol, with
378
393
  index `i`.
379
394
  - **Channel bindings** on real instances (`each "Brick" as i { opacity = bricks[i] }`) →
@@ -62,8 +62,14 @@ per second), `deg(r)` (the inverse, for readouts). Or bind the **`rotationDeg`**
62
62
  var slots = [0, 0, 0]
63
63
  object "P" { x = slots[i] } // computed index
64
64
  slots[i + 1] = 1 // indexed assignment (in actions)
65
+ v = [10, 20, 30][i] // a table written in place, indexed at once
65
66
  ```
66
67
 
68
+ A table written in place — `[a, b, c][i]` — saves a global array for a lookup used once. Its elements and
69
+ its index are expressions, only the element picked is evaluated, and it indexes as an array does (the
70
+ index is rounded; outside the table it is `NaN`, so the binding keeps its fallback). It is not a value on
71
+ its own: `[1, 2, 3]` without an index is an error — to keep a table, declare `var t = [1, 2, 3]`.
72
+
67
73
  ## Functions (`fn`)
68
74
 
69
75
  Define reusable helpers — a **value** function (an expression) or a **procedure** (actions):
@@ -212,8 +212,15 @@ at <x>,<y> // translation -- a COMMA between the two, n
212
212
  matrix(a,b,c,d,e,f) // full affine
213
213
  at center · at center,540 · at 120,center // canvas-relative anchor (resolved from `size`)
214
214
  align <point> of "Name" [offset dx,dy] // pin this item's origin onto another item's bbox
215
+ rotate <deg> · scale <s> · scaleX <s> · scaleY <s> // a FIXED rotation / scale, after `at`
215
216
  ```
216
217
 
218
+ `rotate` and `scale` are written among the attributes that follow `at` — `group "G" at 100,100 pivot 20,0
219
+ rotate 45 scale 2 { … }` — in the units of a `pose`: **degrees** and multipliers. They turn around the
220
+ item's `pivot`, which stays where `at` put it, in whatever order the two are written. They are baked into
221
+ the item's matrix: for a rotation that *changes*, bind the channel (`expr rotation "…"`, or `rotationDeg =
222
+ …` in an `object` block). Not combinable with `align`.
223
+
217
224
  > **Placement & naming gotchas.** The order is fixed: **content → `as "…"` → `at …` / `matrix(…)` →
218
225
  > style attributes** (`font`/`box`/`fill`/…). So `text "…" box W H at x,y` fails — write `text "…" at x,y
219
226
  > box W H`; and `image "logo" 80 80 at 0,0 as "L"` fails — write `image "logo" 80 80 as "L" at 0,0`
package/docs/tooling.md CHANGED
@@ -161,6 +161,15 @@ item, and a roster item no cel ever poses. Each renders an empty frame with no o
161
161
  Render a PNG (skia backend, faithful to the browser). Needs the optional `skia-canvas` dep
162
162
  (`npm i -D skia-canvas`).
163
163
 
164
+ `skia-canvas` 3 (3.0.8 or later) and 4 (from `4.0.0-rc7`) are both accepted. They do not render to the
165
+ same pixels: **version 4 can set a line of text one device pixel higher or lower** than version 3 (half a
166
+ unit at the default scale of 2); shapes are identical. It is a rounding that falls one way or the other
167
+ from one text to the next, not a uniform shift: on a same picture one block may move down, another up, a
168
+ third not at all (measured by a consumer on 545 scenes: 16 identical to the pixel, a median of 0.31% of
169
+ pixels visibly different, 3.4% at most on a page of text). Frames rendered with one must not be mixed
170
+ with frames rendered with the other in the same video, and a pixel comparison between two renders needs
171
+ the same version on both sides. While version 4 is a release candidate, pin the exact version.
172
+
164
173
  ```
165
174
  flatc <file> --render -o out.png [--frame N] [--at k=v[,k2=v2]] [--steps N] [--scale S]
166
175
  ```
@@ -169,6 +178,13 @@ flatc <file> --render -o out.png [--frame N] [--at k=v[,k2=v2]] [--steps N] [--s
169
178
  - `--at score=3,step=2` forces variables → capture a precise state.
170
179
  - **`--steps N`** runs N fixed simulation steps (`every frame`, 60 Hz) *before* capture, so a stateful
171
180
  act unfolds on its own — no need to force every derived ramp variable by hand.
181
+ - **`--script gestures.json`** replays a gesture script first, exactly as `--play` does, and renders the
182
+ state it reaches — the third right answer, the piece dropped, level 2 — instead of a copy of the program
183
+ with other initial `var`s. The order is: `--frame`, `--at`, the script, `--steps`, the capture. A
184
+ **`{ "type": "shot", "name": "after" }`** gesture writes the image *at that point* next to the output
185
+ (`out.after.png`; unnamed shots are numbered), and the output is always the final state. A failed
186
+ `expect` makes the run exit ≠0, images written all the same. `--settle` applies as in `--play`. From
187
+ code: `createRenderer(doc, { interactive: true })`, then `play(gestures)` and `capture()`.
172
188
  - **Embedded fonts render too**: any `asset "id" "font.woff2" font` is registered with skia before
173
189
  capture, so text uses the authored face (matched by the font's intrinsic family name — the same name
174
190
  you put in `text … font "…"`) instead of a host fallback. `.woff2/.woff/.ttf/.otf` are all supported;
@@ -192,7 +208,12 @@ flatc --preview <library.flat> [--symbol NAME] [-o out.flatpack | --render -o ou
192
208
  - `--symbol NAME` picks the symbol (default: the first; others are listed on stderr).
193
209
  - **`--bbox all`** (default) auto-sizes to the UNION of bounds over every frame (sub-timelines unfrozen),
194
210
  so drifting/rotating/growing motion is never clipped. `--bbox frame0` is the old frame-0-only measure;
195
- `--pad N` adds a margin (default 24).
211
+ `--pad N` adds a margin (default 24). The symbol is measured **as it is drawn**: its `expr` channels
212
+ are evaluated with its params — the declared defaults, or the `--set` values — so a bar stretched by
213
+ `expr scaleX "long"` is framed at its real length.
214
+ - **`--frame N` does not move a symbol that has `states`**: its pose is set by the state, not by the
215
+ playhead (only the loops nested in it advance). `flatc` says so; to see a state, or a point between
216
+ two, use `--set <param>=<state name | value>` (`--set pos=0.5` is halfway).
196
217
  - **`--set p=v`** sets the symbol's exposed [params](animating-symbols.md#exposed-parameters-params): a
197
218
  `color` (`hull=#1a5`), a `number`/`bool` (`wave=1.5`), or a `state` by name (`door=open`). Baked into the
198
219
  preview (flatpack + render).
@@ -214,7 +235,7 @@ Use `external` for big media you don't want inflating the JSON; serve the folder
214
235
  Run a scene **without a canvas**, replay a gesture script, and print `{ sends, vars }` — great in CI.
215
236
 
216
237
  ```
217
- flatc <file> --play --script gestures.json [--trace]
238
+ flatc <file> --play --script gestures.json [--trace] [--settle N]
218
239
  ```
219
240
 
220
241
  **Prefer semantic gestures** (by object NAME — robust, the engine resolves coordinates):
@@ -238,10 +259,22 @@ flatc <file> --play --script gestures.json [--trace]
238
259
  - Audio is off in `--play`: a `sound` action is a silent no-op, so a program is replayed as written.
239
260
  - `random()` is seeded in `--play` (seed `1`), so a replay says the same thing twice and an `expect` can
240
261
  assert on a draw. `--seed N` picks another one.
241
- - **`turn`** rotates a `turn`/`turnDeg` target by `angle` (degrees for `turnDeg`, radians for `turn`),
242
- swept in sub-steps so a multi-turn rotation lands. It presses the object where the engine finds it, i.e.
243
- on **whatever is topmost there** — two clock hands overlapping at noon give the gesture to the one on
244
- top. Add **`"from": [x, y]`** to say where the finger lands and pick the other one.
262
+ - **A pointer event takes a frame.** Each press, move and release is followed by one simulation step, as
263
+ a real pointer stays at least one frame on each position — so a rule written in `every frame` sees the
264
+ drag. `--settle N` sets that number for the whole script, and `"settle": N` on a gesture sets it for that
265
+ one. **`--settle 0` is exactly the replay of before 0.38** (`turn` keeps the step it always took between
266
+ its sub-moves). A script that already paces itself with `wait` gestures gets one more frame per event:
267
+ drop the `wait`, or keep the script as it is with `--settle 0`. A script that `expect`s a value which
268
+ decays every frame (a feedback pulse read right after the tap) needs `"settle": 0` on that tap.
269
+ - **`turn`** turns a `turn`/`turnDeg` target **to** `angle`: the value the gesture ENDS at, wherever the
270
+ press was — not a rotation added to the current one. Degrees for `turnDeg`, radians for `turn`; `0` is
271
+ to the right of the pivot, positive is clockwise on screen. It is swept in sub-steps, so several turns
272
+ land (`"angle": 540`). The press goes to the object's position, then to the centre of its drawn box (a
273
+ hand drawn *from* its pivot has its origin on the edge of its shape). **`"from": [x, y]`** names the
274
+ press point — the way to pick one of two hands overlapping at noon. A press that does not grab the
275
+ target is **reported** — a `warnings` entry in the JSON, a `flatc: warning:` line on stderr — naming
276
+ what is grabbed there instead; it used to turn the wrong object, or none, in silence. It is not a
277
+ failure: a script may be proving that a locked dial does not respond.
245
278
  - `set` drives a variable from the host; `wait` runs N fixed 60 Hz steps (advances `every frame` physics).
246
279
  - **`key`** holds a key down (`keys.<name>` reads `1`) for `frames` steps — default `1` — then releases
247
280
  it: the way to test a keyboard-driven scene in CI. Use the authored name (`"ArrowRight"`, `"Space"`).
@@ -275,8 +308,10 @@ with its `file:line`. Nothing is modified, and the exit code is 0.
275
308
 
276
309
  One change is recorded so far. Until 0.36 a path made only of lines (`L`, `H`, `V`) was rounded at its
277
310
  gentle turns; it is now drawn straight, as its data says. The report lists each such path that moves by
278
- 1% of its size or more (`--all` adds the slighter ones), open or closed, with how far it moves — a soft
279
- hill written as four points wants `smooth` back (`path "…" smooth`), a hexagon does not.
311
+ 1% of its size or more, open or closed, with how far it moves — a soft hill written as four points wants
312
+ `smooth` back (`path "…" smooth`), a hexagon does not. An open line is also listed when it moves by 2
313
+ units or more, however little that is of its length: a horizon across the whole frame bends by a few
314
+ units and by a fraction of a percent. `--all` adds the slighter ones.
280
315
 
281
316
  It is a report and not a `--check` warning on purpose: a polyline that is *meant* straight has nothing to
282
317
  add to say so, and would be warned about for ever.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flatkit/compiler",
3
- "version": "0.37.0",
3
+ "version": "0.38.0",
4
4
  "description": "The FlatInk language (parser + AST) and compiler (.flatink → .flatpack). Ships the flatc CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Zwyk Studio",
@@ -57,12 +57,12 @@
57
57
  "docs"
58
58
  ],
59
59
  "dependencies": {
60
- "@flatkit/engine": "0.37.0",
61
- "@flatkit/types": "0.37.0",
62
- "@flatkit/player": "0.37.0"
60
+ "@flatkit/engine": "0.38.0",
61
+ "@flatkit/player": "0.38.0",
62
+ "@flatkit/types": "0.38.0"
63
63
  },
64
64
  "peerDependencies": {
65
- "skia-canvas": "^3.0.8"
65
+ "skia-canvas": "^3.0.8 || ^4.0.0-rc7"
66
66
  },
67
67
  "peerDependenciesMeta": {
68
68
  "skia-canvas": {
@@ -47,13 +47,22 @@ Coordinates are plain numbers; canvas origin is **top-left**. Layers stack botto
47
47
  circle <cx> <cy> <r>
48
48
  ellipse <cx> <cy> <rx> <ry>
49
49
  rect <x> <y> <w> <h> [<r> | <rx> <ry>] // optional rounded corners
50
- path "M0 0 L10 0 L10 10 Z" // raw SVG path data
50
+ path "M0 0 L10 0 L10 10 Z" // raw SVG path data: L is a STRAIGHT line
51
+ path "M0 60 L80 40 L200 52 L320 44" smooth // the same points joined by a curve (a hill, a wave)
52
+ polyline xs ys [count "n"] [closed] // points read from two array vars, every frame
51
53
  text "Hi" font "sans-serif" size 24 align center line 1.2 color #fff box 200 40
52
54
  image "logo" 80 80 at -40,-40 // origin = top-left → center with at -w/2,-h/2
53
- group "Name" at x,y pivot px,py { layer "c" { … } } // nests its own layers
55
+ group "Name" at x,y pivot px,py [rotate deg] [scale s] { layer "c" { … } } // nests its own layers; a FIXED rotate/scale, around the pivot
54
56
  instance "Symbol" as "Name" at x,y // place a symbol from a .flat lib
55
57
  ```
56
58
 
59
+ **A path of lines is drawn straight.** `L`, `H` and `V` join their points with flat segments — a hexagon
60
+ has six flat sides, and a hill written as four points is a broken line. For a curve through the points,
61
+ write **`smooth`** right after the path data (it rounds every turn under 60° and keeps the sharp ones), or
62
+ use real curves (`C`, `Q`, `A`). `flatc --check` only warns about a missing `smooth` from 12 points up:
63
+ on a shorter path nothing tells you. `polyline` takes the NAMES of two arrays (`var xs = fill(50, 0)`);
64
+ its segments are straight too.
65
+
57
66
  Paint / style (work on shapes; most on text & groups too):
58
67
  ```
59
68
  fill #rrggbb | nofill
@@ -263,6 +272,7 @@ feedback lift tilt dim shake(<expr>) // one-liner reactions
263
272
  def gap = 70 // compile-time constant, used via $()
264
273
  repeat i from 0 to 4 { circle $(40 + i*gap) 80 6 fill #ffd98a } // $(expr) = compile-time arithmetic
265
274
  symbol "Card"(label, tint = "#fff") { … text "$(label)" … fill $(tint) … } // parameterized symbol
275
+ symbol "Dot" { layer "a" { circle 0 0 10 fill #c33 } } // a plain symbol may live in the program too: instance "Dot" as "D" at 100,100
266
276
  instance "Card"($(i+1)) as "C$(i)" at $(80 + i*90),200
267
277
  each "Key" as i { when clicked { input = input*10 + (i+1) } } // shared behavior over instances
268
278
  match Word1, Word2 onto Good, Bad { // declarative drag+drop pairing
@@ -276,7 +286,7 @@ align top of "Bin" [offset dx,dy] // pin origin onto ano
276
286
  ## Expressions & stdlib
277
287
 
278
288
  Pure & numeric (no booleans: comparisons/logic yield `1`/`0`). Operators: `?: || && == != < > <= >=
279
- + - * / % - ! . [] fn()`.
289
+ + - * / % - ! . [] fn()`. A lookup table can be written in place and indexed at once: `[10, 20, 30][i]`.
280
290
  Built-ins: `sin cos tan asin acos atan atan2 abs sqrt pow exp log floor ceil round sign min max hypot
281
291
  clamp(x,lo,hi) lerp(a,b,t) mod(a,b) between(x,lo,hi) rad(deg) deg(rad) turns(n)`. Constants `PI TAU E`.
282
292
  Reserved: `time` (seconds, **wraps** every `durationFrames`), `clock` (seconds, **monotone**), `frame`,
@@ -34,9 +34,12 @@ every frame { if score >= 10 { send "win" } }
34
34
  circle cx cy r · ellipse cx cy rx ry · rect x y w h [r | rx ry] · path "M0 0 L10 0 L10 10 Z"
35
35
  text "Hi" font "sans-serif" size 24 align center line 1.2 color #fff box 200 40 [bold] [italic] [wrap]
36
36
  image "id" w h at -w/2,-h/2 // origin top-left → center yourself ; needs: asset "id" "f.png" image
37
- group "Name" at x,y pivot px,py { layer "c" { … } } // nests its own layers
37
+ group "Name" at x,y pivot px,py [rotate deg] [scale s] { layer "c" { … } } // nests its own layers
38
38
  instance "Symbol" as "Name" at x,y // place a symbol from a .flat
39
+ polyline xs ys [count "n"] [closed] // straight segments through two array vars, live
39
40
  ```
41
+ A path of lines (`L`/`H`/`V`) is drawn **straight**; for a curve through the points add `smooth` after the
42
+ data: `path "M0 60 L80 40 L200 52" smooth`. `--check` warns about a missing `smooth` only from 12 points up.
40
43
  Style: `fill #rrggbb | nofill` · `stroke #rgb <w> [cap round][join round][dash a,b]` · `opacity 0..1` ·
41
44
  `draw <0..1> [from <0..1>]` (stroke extent by ARC LENGTH; quoted = expression: `draw "avance"` = ink drawn
42
45
  behind a finger, the measure `trace` reports) ·
@@ -99,7 +102,7 @@ State/funcs: `var a = 0` · `var arr = fill(8,0)` (also as an ASSIGNMENT: `arr =
99
102
  · `each "Key" as i { when clicked { … } }` · `at center` · `align top of "Bin" [offset dx,dy]`.
100
103
 
101
104
  ## Expressions
102
- Pure numeric, no booleans (compare/logic → 1/0). Ops: `?: || && == != < > <= >= + - * / % - ! . [] fn()`.
105
+ Pure numeric, no booleans (compare/logic → 1/0). Ops: `?: || && == != < > <= >= + - * / % - ! . [] fn()` · inline table `[10, 20, 30][i]`.
103
106
  Funcs: `sin cos tan atan2 abs sqrt pow floor ceil round sign min max hypot clamp(x,lo,hi) lerp(a,b,t)
104
107
  mod(a,b) between(x,lo,hi) rad(deg) deg(rad) turns(n)`. Const `PI TAU E`.
105
108
  Reserved: `time`(s, **wraps**) `clock`(s, **monotone**) `frame` `value` `mouse.x/y` `keys.<Key>` `self.*` `<Name>.*`.
@@ -84,14 +84,18 @@ symbol "Boat" {
84
84
  3. **`stroke`, `opacity`, `filter` all exist on `path` AND `text`.** Outline text with
85
85
  `text "…" color #fff stroke #000 4 join round` (stroke drawn behind fill) — never stack two texts.
86
86
  4. **`image` origin is top-left** → center with `at -w/2,-h/2`.
87
- 5. **Text never wraps unless you add `wrap`** (`… box W H wrap`); otherwise only explicit `\n` breaks.
87
+ 5. **A path of lines is drawn STRAIGHT.** `L`/`H`/`V` are flat segments: a hill, a wave or a blob written
88
+ as a few points comes out as a broken line. For a curve through the points write `smooth` after the
89
+ data — `path "M0 60 L80 40 L200 52 L320 44" smooth nofill stroke #fff 3` — or use `C`/`Q`/`A`.
90
+ `flatc --check` warns about a missing `smooth` only from 12 points up; below that, nothing tells you.
91
+ 6. **Text never wraps unless you add `wrap`** (`… box W H wrap`); otherwise only explicit `\n` breaks.
88
92
  Also, `at x,y` on `text`/`image` must come **right after the content**, before `font`/`box`/`fill`
89
93
  (`text "…" at x,y box W H`, not `… box W H at x,y`).
90
- 6. **Filters are cheap only on static decor** (auto-cached). Fine here since your assets are static —
94
+ 7. **Filters are cheap only on static decor** (auto-cached). Fine here since your assets are static —
91
95
  but a large blurred plane is costly; for big soft shadows prefer a "baked" offset copy in `#00000028`.
92
- 7. **Layer order = z-order.** Bottom layer drawn first. Put shadows/backings on lower layers.
93
- 8. **Don't hand-compute Bezier circles** — use `circle`/`ellipse`/`rect` sugar; they normalize to paths.
94
- 9. **Center art on `0,0`** so the asset rotates/scales cleanly when someone animates it later.
96
+ 8. **Layer order = z-order.** Bottom layer drawn first. Put shadows/backings on lower layers.
97
+ 9. **Don't hand-compute Bezier circles** — use `circle`/`ellipse`/`rect` sugar; they normalize to paths.
98
+ 10. **Center art on `0,0`** so the asset rotates/scales cleanly when someone animates it later.
95
99
 
96
100
  ## Self-check
97
101
  - File is only `symbol "…" { … }` blocks, no `size`, no behavior.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/compile.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// compile.ts — the \"modern SWF\" compiler (RFC, step P2).\n//\n// Takes the PROGRAM (`.flatink`) + the ASSETS (`.flat`) and produces a resolved `Doc` —\n// the \".flatpack\" v1 (= the baked doc the player already plays). Steps:\n// 1. parse the assets → symbols;\n// 2. parse the program → composition + behavior (`@Name` refs);\n// 3. RESOLVE refs by name (instances → symbol id), across ALL libs;\n// 4. assemble the Doc.\n//\n// v1: no binary optimization nor media embedding (P2b). Pure, no DOM.\n// ─────────────────────────────────────────────────────────────────────────────\nimport type { Doc, Item, Layer } from '@flatkit/types'\nimport { parseFlatLib, parseProgramFull } from '@flatkit/engine/flatFormat'\nimport { isGroup, isInstance } from '@flatkit/engine/layers'\n\n/** Resolves `symbolId: '@Name'` instances into real ids (recursive, across groups). */\nfunction resolveRefs(layers: Layer[], byName: Map<string, string>): void {\n const walk = (items: Item[]) => {\n for (const it of items) {\n if (isInstance(it) && it.symbolId.startsWith('@')) it.symbolId = byName.get(it.symbolId.slice(1)) ?? it.symbolId\n if (isGroup(it)) it.layers.forEach((l) => walk(l.items))\n }\n }\n layers.forEach((l) => walk(l.items))\n}\n\n/** Source of a media (base64 data-URI) by declared path (`asset … \"path\" …`). */\nexport type MediaMap = Record<string, { mime: string; data: string }>\n\n/**\n * Compile a `.flatink` program + its `.flat` assets → playable `Doc` (the \".flatpack\").\n * `assetSrcs` = the text of each `.flat` lib. `media` = the content of the referenced media files\n * (by path). Symbol refs (by name) are resolved across the whole set of libs; declared media\n * are EMBEDDED (path → data-URI).\n */\nexport function compileFlatpack(programSrc: string, assetSrcs: string[] = [], media: MediaMap = {}): Doc {\n const libs = assetSrcs.map((src) => parseFlatLib(src))\n const symbols = libs.flatMap((l) => l.symbols)\n const folders = libs.flatMap((l) => l.folders) // library folders (organization)\n const prog = parseProgramFull(programSrc)\n const byName = new Map(symbols.map((s) => [s.name, s.id]))\n symbols.forEach((s) => resolveRefs(s.layers, byName)) // cross-lib refs\n resolveRefs(prog.layers, byName) // program refs\n\n // Embed declared media (data = path → provided data-URI). Missing = left as is.\n const assets = (prog.assets ?? []).map((a) => {\n const m = media[a.data]\n const r = m ? { ...a, mime: m.mime, data: m.data } : a\n // A font without an explicit family defaults to its declared id — which is exactly what text targets via\n // `font \"<id>\"`. Gives consumers an explicit `family` instead of relying on a `family || id` fallback.\n return r.kind === 'font' && !r.family ? { ...r, family: r.id } : r\n })\n\n return {\n width: prog.width,\n height: prog.height,\n ...(prog.background ? { background: prog.background } : {}),\n symbols,\n ...(folders.length ? { folders } : {}),\n layers: prog.layers,\n ...(prog.variables && Object.keys(prog.variables).length ? { variables: prog.variables } : {}),\n ...(prog.imports?.length ? { imports: prog.imports } : {}),\n ...(prog.functions?.length ? { functions: prog.functions } : {}),\n timeline: prog.timeline ?? { fps: 24, durationFrames: 60, tracks: [] },\n ...(prog.interactions?.length ? { interactions: prog.interactions } : {}),\n ...(prog.interactors?.length ? { interactors: prog.interactors } : {}),\n ...(assets.length ? { assets } : {}),\n }\n}\n\n/** Serialize the `.flatpack` v1 (JSON of the compiled Doc). */\nexport const packToJSON = (doc: Doc): string => JSON.stringify(doc)\n"],"mappings":";AAaA,SAAS,cAAc,wBAAwB;AAC/C,SAAS,SAAS,kBAAkB;AAGpC,SAAS,YAAY,QAAiB,QAAmC;AACvE,QAAM,OAAO,CAAC,UAAkB;AAC9B,eAAW,MAAM,OAAO;AACtB,UAAI,WAAW,EAAE,KAAK,GAAG,SAAS,WAAW,GAAG,EAAG,IAAG,WAAW,OAAO,IAAI,GAAG,SAAS,MAAM,CAAC,CAAC,KAAK,GAAG;AACxG,UAAI,QAAQ,EAAE,EAAG,IAAG,OAAO,QAAQ,CAAC,MAAM,KAAK,EAAE,KAAK,CAAC;AAAA,IACzD;AAAA,EACF;AACA,SAAO,QAAQ,CAAC,MAAM,KAAK,EAAE,KAAK,CAAC;AACrC;AAWO,SAAS,gBAAgB,YAAoB,YAAsB,CAAC,GAAG,QAAkB,CAAC,GAAQ;AACvG,QAAM,OAAO,UAAU,IAAI,CAAC,QAAQ,aAAa,GAAG,CAAC;AACrD,QAAM,UAAU,KAAK,QAAQ,CAAC,MAAM,EAAE,OAAO;AAC7C,QAAM,UAAU,KAAK,QAAQ,CAAC,MAAM,EAAE,OAAO;AAC7C,QAAM,OAAO,iBAAiB,UAAU;AACxC,QAAM,SAAS,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;AACzD,UAAQ,QAAQ,CAAC,MAAM,YAAY,EAAE,QAAQ,MAAM,CAAC;AACpD,cAAY,KAAK,QAAQ,MAAM;AAG/B,QAAM,UAAU,KAAK,UAAU,CAAC,GAAG,IAAI,CAAC,MAAM;AAC5C,UAAM,IAAI,MAAM,EAAE,IAAI;AACtB,UAAM,IAAI,IAAI,EAAE,GAAG,GAAG,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,IAAI;AAGrD,WAAO,EAAE,SAAS,UAAU,CAAC,EAAE,SAAS,EAAE,GAAG,GAAG,QAAQ,EAAE,GAAG,IAAI;AAAA,EACnE,CAAC;AAED,SAAO;AAAA,IACL,OAAO,KAAK;AAAA,IACZ,QAAQ,KAAK;AAAA,IACb,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,IACzD;AAAA,IACA,GAAI,QAAQ,SAAS,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpC,QAAQ,KAAK;AAAA,IACb,GAAI,KAAK,aAAa,OAAO,KAAK,KAAK,SAAS,EAAE,SAAS,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;AAAA,IAC5F,GAAI,KAAK,SAAS,SAAS,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,IACxD,GAAI,KAAK,WAAW,SAAS,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;AAAA,IAC9D,UAAU,KAAK,YAAY,EAAE,KAAK,IAAI,gBAAgB,IAAI,QAAQ,CAAC,EAAE;AAAA,IACrE,GAAI,KAAK,cAAc,SAAS,EAAE,cAAc,KAAK,aAAa,IAAI,CAAC;AAAA,IACvE,GAAI,KAAK,aAAa,SAAS,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;AAAA,IACpE,GAAI,OAAO,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,EACpC;AACF;AAGO,IAAM,aAAa,CAAC,QAAqB,KAAK,UAAU,GAAG;","names":[]}