@field/cascade 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/AGENTS.md +7 -4
  2. package/ARCHITECTURE.md +5 -4
  3. package/CHANGELOG.md +195 -0
  4. package/README.md +1 -1
  5. package/dist/assets/AgentPanel-1JugFjE4.css +1 -0
  6. package/dist/assets/AgentPanel-BDL5MddA.js +5 -0
  7. package/dist/assets/{CodeEditor-4O7FQ__R.js → CodeEditor-C-xYG-Wn.js} +120 -120
  8. package/dist/assets/CodePanel-CtXd50m6.js +2 -0
  9. package/dist/assets/CookInfoPanel-B3NMhKJF.css +1 -0
  10. package/dist/assets/CookInfoPanel-DG4_TRsX.js +2 -0
  11. package/dist/assets/CustomNodeDialog-CHfZUs-r.js +1 -0
  12. package/dist/assets/DefinitionNode-CaRTXQ_b.js +1 -0
  13. package/dist/assets/DefinitionPanel-C3CxZY9r.js +1 -0
  14. package/dist/assets/DefinitionPanel-VNLnAiQD.css +1 -0
  15. package/dist/assets/OpenGraphDialog-BLdr6k_q.js +1 -0
  16. package/dist/assets/ProjectSettings-ZnvmIAir.js +2 -0
  17. package/dist/assets/{SettingsDialog-Bz8JxISH.js → SettingsDialog-CBX3Wa87.js} +1 -1
  18. package/dist/assets/TimelinePanel-CDeR0arY.css +1 -0
  19. package/dist/assets/TimelinePanel-CWgnFH1e.js +1 -0
  20. package/dist/assets/{VersionHistoryDialog-D5Z0nJ5-.js → VersionHistoryDialog-BOSoW1R-.js} +1 -1
  21. package/dist/assets/assert-BO2_erPD.js +1 -0
  22. package/dist/assets/assert-hvykGCnP.js +1 -0
  23. package/dist/assets/builder-BmBKcDix.js +1 -0
  24. package/dist/assets/camera-DFF7YA3F.js +1 -0
  25. package/dist/assets/checks-DAvyO9Gf.js +1 -0
  26. package/dist/assets/contactSheet-N1NcaWxw.js +2 -0
  27. package/dist/assets/{core-Yqi0Ni61.js → core-DB-MkGjj.js} +3 -4
  28. package/dist/assets/coreTypes-DtnDATZ7.js +1 -0
  29. package/dist/assets/{cssMode-CxUzc4vp.js → cssMode-DPVckSOc.js} +1 -1
  30. package/dist/assets/definitions-B227e7f_.js +1 -0
  31. package/dist/assets/definitions-D0WOUJd7.js +1 -0
  32. package/dist/assets/{editor.api-BNFb25CX.js → editor.api-Dkutg_Z1.js} +1 -1
  33. package/dist/assets/expressions-X-Z-GrgW.js +5 -0
  34. package/dist/assets/file-DAL6IfjV.js +1 -0
  35. package/dist/assets/filters-D3k1LRMa.js +1 -0
  36. package/dist/assets/{freemarker2-0ZDnWxeM.js → freemarker2-CLiLKk3B.js} +1 -1
  37. package/dist/assets/generators-BhDxRAFC.js +1 -0
  38. package/dist/assets/geo-BsMImJIJ.js +1 -0
  39. package/dist/assets/geo-C4YngDob.js +2 -0
  40. package/dist/assets/{handlebars-Cy18MoaD.js → handlebars-CcD3aMeL.js} +1 -1
  41. package/dist/assets/{html-B-2Dx7nq.js → html-DvZWGfqb.js} +1 -1
  42. package/dist/assets/{htmlMode-DUAc9B_d.js → htmlMode-DUD3VaoN.js} +1 -1
  43. package/dist/assets/image-0SfH-snh.js +1 -0
  44. package/dist/assets/image-Ck-RdGU4.js +2 -0
  45. package/dist/assets/index-BWnrmIB9.js +137 -0
  46. package/dist/assets/index-DIaxEONI.css +1 -0
  47. package/dist/assets/io-BGmt2g0D.js +1 -0
  48. package/dist/assets/{javascript-DeQsDqP5.js → javascript-Cap2ahgh.js} +1 -1
  49. package/dist/assets/{jsonMode-BiFwJVSk.js → jsonMode-B0uLjp9C.js} +1 -1
  50. package/dist/assets/{liquid-BLoHH68a.js → liquid-CnxwhfLn.js} +1 -1
  51. package/dist/assets/{lspLanguageFeatures-CNFgm1Kv.js → lspLanguageFeatures-Dn9hlKX6.js} +1 -1
  52. package/dist/assets/math-GeULS-sk.js +1 -0
  53. package/dist/assets/{mdx-CFXQVNfs.js → mdx-BV3jbrVa.js} +1 -1
  54. package/dist/assets/pop-KMF_qloi.js +1 -0
  55. package/dist/assets/preload-helper-Czpn1I53.js +1 -0
  56. package/dist/assets/{python-Cf7S7p2E.js → python-CGyFk4XK.js} +1 -1
  57. package/dist/assets/{razor-CMHBcG-g.js → razor-CQfzX3-N.js} +1 -1
  58. package/dist/assets/remap-5SjXQU-q.js +1 -0
  59. package/dist/assets/renderMarkdown-ork0Oir7.js +67 -0
  60. package/dist/assets/studioImages-CAyH-0Pl.js +1 -0
  61. package/dist/assets/text-ZpX7qNkx.js +2 -0
  62. package/dist/assets/time-P_OiBhcf.js +1 -0
  63. package/dist/assets/{tsMode-BEih0GDj.js → tsMode-BOeXzO9H.js} +1 -1
  64. package/dist/assets/{typescript-COEKYD_a.js → typescript-J1SnE_Tu.js} +1 -1
  65. package/dist/assets/{workers-BNhAhMNn.js → workers-CYwwTiVF.js} +1 -1
  66. package/dist/assets/{xml-CPiMpnPl.js → xml-DnPRNzK9.js} +1 -1
  67. package/dist/assets/{yaml-Bgp1ITLR.js → yaml-BmmYBNa9.js} +1 -1
  68. package/dist/cli/DefinitionNode-P7AA4TAU.js +15 -0
  69. package/dist/cli/{Graph-ZJJTRQTM.js → Graph-OB6EWSGL.js} +12 -12
  70. package/dist/cli/assert-GROGQNCQ.js +25 -0
  71. package/dist/cli/checks-3WB44XBL.js +189 -0
  72. package/dist/cli/{chunk-RQZ4SX5R.js → chunk-2JEU3MGB.js} +2 -2
  73. package/dist/cli/{chunk-WKURDVYP.js → chunk-3RXL5GHB.js} +28 -331
  74. package/dist/cli/chunk-53LGFPWX.js +138 -0
  75. package/dist/cli/chunk-5ORUQ6ES.js +267 -0
  76. package/dist/cli/chunk-73S4JZXK.js +336 -0
  77. package/dist/cli/{chunk-YAT5Y25U.js → chunk-7QE2ZNSN.js} +3 -3
  78. package/dist/cli/{chunk-SGJAET2L.js → chunk-BPODRH52.js} +8 -4
  79. package/dist/cli/{chunk-XWDSKG7N.js → chunk-BZ63XQ3I.js} +2 -2
  80. package/dist/cli/{chunk-FCDXJ6EK.js → chunk-CTPLRSWF.js} +2 -13
  81. package/dist/cli/chunk-EU4ZFZ7Y.js +122 -0
  82. package/dist/cli/{chunk-WIH4TEIS.js → chunk-EZRWJ4G2.js} +1 -1
  83. package/dist/cli/{chunk-BIJO5OFS.js → chunk-FWMSGKEH.js} +1 -1
  84. package/dist/cli/{chunk-POMMNKZZ.js → chunk-G23A2BZZ.js} +38 -5
  85. package/dist/cli/{chunk-35D343DC.js → chunk-GRIN2DDP.js} +19 -1
  86. package/dist/cli/chunk-JVOCK5W4.js +83 -0
  87. package/dist/cli/chunk-KBGUO27Q.js +15 -0
  88. package/dist/cli/{chunk-GFS6W3C5.js → chunk-L6B6FUQP.js} +54 -32
  89. package/dist/cli/chunk-LTXWBXOM.js +438 -0
  90. package/dist/cli/chunk-MI5JLQYA.js +220 -0
  91. package/dist/cli/{chunk-EUIAC3U3.js → chunk-MP653A5O.js} +884 -124
  92. package/dist/cli/{chunk-A4OAMOZH.js → chunk-NJFMDXHR.js} +13 -3
  93. package/dist/cli/{chunk-3JTJHF7V.js → chunk-OM6BEME6.js} +4 -4
  94. package/dist/cli/{chunk-DUIUFVU2.js → chunk-ST6LROUG.js} +49 -23
  95. package/dist/cli/{chunk-RLCAJBVR.js → chunk-TJW67QSI.js} +8 -7
  96. package/dist/cli/chunk-WGKPJZDY.js +364 -0
  97. package/dist/cli/{chunk-D6BUOHUG.js → chunk-X4WKAOQP.js} +4 -4
  98. package/dist/cli/{chunk-VVGSLVSF.js → chunk-X566FWBV.js} +6 -1
  99. package/dist/cli/chunk-YH637PDL.js +317 -0
  100. package/dist/cli/chunk-ZSCUZE6I.js +112 -0
  101. package/dist/cli/{cliCommands-UQSFGEHF.js → cliCommands-JJN5Z3ZC.js} +23 -17
  102. package/dist/cli/{commands-Q23M26QM.js → commands-PHYFYA3F.js} +129 -47
  103. package/dist/cli/contactSheet-2PQVRUFW.js +11 -0
  104. package/dist/cli/{definitions-HF2FTHYU.js → definitions-BPOPW52L.js} +7 -7
  105. package/dist/cli/{doctorCommand-B7BPQMN6.js → doctorCommand-ORWQRNYP.js} +36 -6
  106. package/dist/cli/{edit-JHCPTQFT.js → edit-S5KEQ5RA.js} +3 -3
  107. package/dist/cli/editorCommands-NFR32JOY.js +41 -0
  108. package/dist/cli/{encode-HAUZTYPT.js → encode-ICPEPMH5.js} +2 -2
  109. package/dist/cli/file-3HTQHPYQ.js +41 -0
  110. package/dist/cli/filters-6ODFWYK4.js +379 -0
  111. package/dist/cli/generators-YU7GB4MD.js +210 -0
  112. package/dist/cli/{geo-4RNK3QVN.js → geo-6ZPGKZRU.js} +7 -7
  113. package/dist/cli/{graphCommands-6BMAKGQE.js → graphCommands-EOUOBOIJ.js} +4 -4
  114. package/dist/cli/{graphFile-VPSQ6OF7.js → graphFile-YTGJB5F5.js} +25 -19
  115. package/dist/cli/{headlessCanvas-5XLDZT7M.js → headlessCanvas-ENRHC7GS.js} +3 -2
  116. package/dist/cli/{helpCommand-3VRE3KVO.js → helpCommand-PULH6ZYB.js} +9 -7
  117. package/dist/cli/image-TXELZZK5.js +55 -0
  118. package/dist/cli/index.js +14 -14
  119. package/dist/cli/{math-ZRMLKU2D.js → math-RSYRQ3C6.js} +5 -5
  120. package/dist/cli/{mcp-ZLEEMYGF.js → mcp-JVZFQOEX.js} +4 -4
  121. package/dist/cli/{parity-KINARCP2.js → parity-X6DEH2DV.js} +42 -36
  122. package/dist/cli/{pop-USDFXB7H.js → pop-B7SIIYKN.js} +5 -5
  123. package/dist/cli/project-VYGIA7JX.js +12 -0
  124. package/dist/cli/{projectDefinition-DMTZN2NE.js → projectDefinition-F3D7UIHJ.js} +8 -8
  125. package/dist/cli/{projectRuntime-Z5PJOH47.js → projectRuntime-2Q4JKEH3.js} +19 -12
  126. package/dist/cli/registerStandardNodes-BXJCB6E4.js +744 -0
  127. package/dist/cli/{render-YURAPPUI.js → render-PT4MWI6U.js} +24 -22
  128. package/dist/cli/{runCommand-PI764LEG.js → runCommand-HNPQVZMS.js} +3 -3
  129. package/dist/cli/runner-5XPXVW7J.js +43 -0
  130. package/dist/cli/runtime/image.ts +27 -2
  131. package/dist/cli/runtime/io.ts +78 -0
  132. package/dist/cli/share-MHF6TNRK.js +166 -0
  133. package/dist/cli/stageCache-TY2BFZJR.js +15 -0
  134. package/dist/cli/{stageRunner-KUNIMQKJ.js → stageRunner-KY2EJP7X.js} +2 -2
  135. package/dist/cli/text-MB3EKR4H.js +126 -0
  136. package/dist/cli/{verbs-RCE3K4VQ.js → verbs-PI2JH5FI.js} +2 -2
  137. package/dist/cli/webBuild-WPPMGGW7.js +44 -0
  138. package/dist/contracts/capabilities.d.ts +18 -1
  139. package/dist/contracts/color.d.ts +8 -0
  140. package/dist/contracts/color.js +23 -0
  141. package/dist/contracts/definition.d.ts +10 -0
  142. package/dist/contracts/schema.d.ts +1 -1
  143. package/dist/contracts/schema.js +1 -0
  144. package/dist/contracts/validate.js +11 -1
  145. package/dist/contracts/values.d.ts +24 -0
  146. package/dist/image.d.ts +15 -0
  147. package/dist/image.js +36 -2
  148. package/dist/index.html +8 -2
  149. package/dist/io.d.ts +29 -0
  150. package/dist/io.js +40 -0
  151. package/dist/player/app.js +3 -2
  152. package/dist/player/editor.js +3 -2
  153. package/dist/player/editorEngine.js +3 -2
  154. package/dist/runtime/builtins/assert/checks.d.ts +18 -0
  155. package/dist/runtime/builtins/assert/checks.js +207 -0
  156. package/dist/runtime/builtins/assert/definitions.d.ts +130 -0
  157. package/dist/runtime/builtins/assert/definitions.js +54 -0
  158. package/dist/runtime/builtins/assert/index.d.ts +152 -0
  159. package/dist/runtime/builtins/assert/index.js +36 -0
  160. package/dist/runtime/builtins/image/contactSheet.d.ts +3 -165
  161. package/dist/runtime/builtins/image/contactSheet.js +0 -49
  162. package/dist/runtime/builtins/image/definitions.d.ts +950 -0
  163. package/dist/runtime/builtins/image/definitions.js +343 -0
  164. package/dist/runtime/builtins/image/file.d.ts +8 -0
  165. package/dist/runtime/builtins/image/file.js +37 -0
  166. package/dist/runtime/builtins/image/filters.d.ts +8 -0
  167. package/dist/runtime/builtins/image/filters.js +394 -0
  168. package/dist/runtime/builtins/image/generators.d.ts +7 -0
  169. package/dist/runtime/builtins/image/generators.js +144 -0
  170. package/dist/runtime/builtins/image/index.d.ts +947 -4
  171. package/dist/runtime/builtins/image/index.js +41 -6
  172. package/dist/runtime/builtins/image/io.d.ts +100 -0
  173. package/dist/runtime/builtins/image/io.js +142 -0
  174. package/dist/runtime/builtins/image/planar.d.ts +55 -0
  175. package/dist/runtime/builtins/image/planar.js +274 -0
  176. package/dist/runtime/builtins/image/simplex.d.ts +22 -0
  177. package/dist/runtime/builtins/image/simplex.js +92 -0
  178. package/dist/runtime/builtins/image/text.d.ts +4 -0
  179. package/dist/runtime/builtins/image/text.js +141 -0
  180. package/dist/runtime/builtins/index.d.ts +3 -0
  181. package/dist/runtime/builtins/index.js +7 -0
  182. package/dist/runtime/gpu.d.ts +25 -0
  183. package/dist/runtime/gpu.js +40 -0
  184. package/dist/runtime/runtime.d.ts +2 -0
  185. package/dist/runtime/runtime.js +268 -44
  186. package/dist/runtime/types.d.ts +30 -0
  187. package/doc/CLI_REFERENCE.md +21 -3
  188. package/doc/EDITOR_EXPORT.md +26 -0
  189. package/doc/MCP.md +2 -1
  190. package/doc/NODE_REFERENCE.md +286 -14
  191. package/doc/PROJECT_AUTHORING.md +98 -3
  192. package/package.json +4 -1
  193. package/dist/assets/CodePanel-BU-autmq.js +0 -2
  194. package/dist/assets/CustomNodeDialog-BRpDfOV5.js +0 -1
  195. package/dist/assets/OpenGraphDialog-BwbRlC0X.js +0 -1
  196. package/dist/assets/ProjectSettings-DLOXLxcA.js +0 -2
  197. package/dist/assets/definitions-GreEMXLV.js +0 -1
  198. package/dist/assets/geo-u0n_jOsD.js +0 -1
  199. package/dist/assets/image-cAa-Nu9O.js +0 -2062
  200. package/dist/assets/index-5ZtDAuy2.js +0 -220
  201. package/dist/assets/index-7vae6YFC.css +0 -1
  202. package/dist/assets/math-DioIQOPJ.js +0 -1
  203. package/dist/assets/pop-DC_TQG9t.js +0 -1
  204. package/dist/cli/chunk-36P5K3SD.js +0 -631
  205. package/dist/cli/chunk-4UDFYF5G.js +0 -132
  206. package/dist/cli/chunk-53NMBJAO.js +0 -35
  207. package/dist/cli/chunk-MIIEFUGG.js +0 -623
  208. package/dist/cli/chunk-QBBKRNGY.js +0 -90
  209. package/dist/cli/chunk-WPUDGEFY.js +0 -20
  210. package/dist/cli/editorCommands-UIF5YLUZ.js +0 -33
  211. package/dist/cli/project-K3QIPGBB.js +0 -12
  212. package/dist/cli/registerStandardNodes-S2VLMF6J.js +0 -3081
  213. package/dist/cli/runner-RA6UKEPD.js +0 -37
  214. package/dist/cli/webBuild-GT4PZT5D.js +0 -37
  215. package/dist/runtime/builtins/headless.d.ts +0 -19
  216. package/dist/runtime/builtins/headless.js +0 -25
@@ -0,0 +1,207 @@
1
+ /** The bodies of `cascade.assert.*`; the declarations are in `index.ts`. */
2
+ class AssertionFailed extends Error {
3
+ constructor(nodeId, moduleId, detail, message) {
4
+ const extra = typeof message === "string" && message.trim() ? ` ${message.trim()}` : "";
5
+ super(`Assertion failed in "${nodeId}" (${moduleId}): ${detail}.${extra}`);
6
+ this.name = "AssertionFailed";
7
+ }
8
+ }
9
+ export function executeRange({ nodeId, inputs, props, outputs }) {
10
+ const value = inputs.value;
11
+ if (typeof value !== "number" || !Number.isFinite(value)) {
12
+ throw new AssertionFailed(nodeId, "cascade.assert.Range", `value ${String(value)} is not a finite number`, props.message);
13
+ }
14
+ if (value < props.min || value > props.max) {
15
+ throw new AssertionFailed(nodeId, "cascade.assert.Range", `value ${value} is outside [${props.min}, ${props.max}]`, props.message);
16
+ }
17
+ outputs.value.set(value);
18
+ }
19
+ export function executeEquals({ nodeId, inputs, props, outputs }) {
20
+ if (!deepEqual(inputs.value, props.expected, props.tolerance)) {
21
+ throw new AssertionFailed(nodeId, "cascade.assert.Equals", `got ${preview(inputs.value)}, expected ${preview(props.expected)}`, props.message);
22
+ }
23
+ outputs.value.set(inputs.value);
24
+ }
25
+ export function executeTrue({ nodeId, inputs, props, outputs }) {
26
+ if (inputs.value !== true)
27
+ throw new AssertionFailed(nodeId, "cascade.assert.True", `value is ${preview(inputs.value)}`, props.message);
28
+ outputs.value.set(true);
29
+ }
30
+ export async function executeNotBlank({ nodeId, inputs, props, outputs, capabilities, signal }) {
31
+ const image = inputs.image;
32
+ const ref = typeof image === "string" ? { path: image } : image;
33
+ if (!ref || typeof ref.path !== "string" || !ref.path) {
34
+ throw new AssertionFailed(nodeId, "cascade.assert.NotBlank", "no image arrived", props.message);
35
+ }
36
+ const path = ref.path;
37
+ const kept = capabilities.assets.imagePixels?.({ path });
38
+ const pixels = kept
39
+ ? { width: kept.width, height: kept.height, channels: 4, data: kept.data }
40
+ : await decodeImage(await capabilities.assets.read({ path }, { signal }));
41
+ if (!pixels)
42
+ throw new AssertionFailed(nodeId, "cascade.assert.NotBlank", `${path} is not a PNG or PPM this node can read`, props.message);
43
+ const verdict = blankness(pixels, props.tolerance);
44
+ if (verdict)
45
+ throw new AssertionFailed(nodeId, "cascade.assert.NotBlank", `${path} (${pixels.width}x${pixels.height}) is ${verdict}`, props.message);
46
+ outputs.image.set(inputs.image);
47
+ }
48
+ function preview(value) {
49
+ const text = JSON.stringify(value) ?? String(value);
50
+ return text.length > 120 ? `${text.slice(0, 117)}...` : text;
51
+ }
52
+ function deepEqual(a, b, tolerance) {
53
+ if (typeof a === "number" && typeof b === "number")
54
+ return a === b || Math.abs(a - b) <= tolerance;
55
+ if (Array.isArray(a) && Array.isArray(b))
56
+ return a.length === b.length && a.every((item, index) => deepEqual(item, b[index], tolerance));
57
+ if (a && b && typeof a === "object" && typeof b === "object" && !Array.isArray(a) && !Array.isArray(b)) {
58
+ const left = a;
59
+ const right = b;
60
+ const keys = Object.keys(left);
61
+ if (keys.length !== Object.keys(right).length)
62
+ return false;
63
+ return keys.every((key) => key in right && deepEqual(left[key], right[key], tolerance));
64
+ }
65
+ return a === b || (a == null && b == null);
66
+ }
67
+ /** 'one flat colour', 'fully transparent', or null when the image has content. */
68
+ export function blankness(pixels, tolerance = 0) {
69
+ const { channels, data } = pixels;
70
+ const alpha = channels === 2 || channels === 4 ? channels - 1 : -1;
71
+ const colour = alpha === -1 ? channels : channels - 1;
72
+ const min = new Array(channels).fill(255);
73
+ const max = new Array(channels).fill(0);
74
+ let opaque = alpha === -1;
75
+ for (let index = 0; index < data.length; index += channels) {
76
+ if (alpha !== -1 && data[index + alpha] > tolerance)
77
+ opaque = true;
78
+ for (let channel = 0; channel < channels; channel += 1) {
79
+ const value = data[index + channel];
80
+ if (value < min[channel])
81
+ min[channel] = value;
82
+ if (value > max[channel])
83
+ max[channel] = value;
84
+ }
85
+ }
86
+ if (!opaque)
87
+ return "fully transparent";
88
+ const spread = Math.max(...max.map((value, channel) => value - min[channel]));
89
+ if (spread <= tolerance)
90
+ return colour === 1 ? `one flat grey (${min[0]})` : `one flat colour (rgb ${min.slice(0, 3).join(", ")})`;
91
+ return null;
92
+ }
93
+ /** 8-bit non-interlaced PNG (grey, grey+alpha, RGB, RGBA) or binary PPM/PGM. */
94
+ export async function decodeImage(bytes) {
95
+ if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47)
96
+ return decodePng(bytes);
97
+ if (bytes[0] === 0x50 && (bytes[1] === 0x36 || bytes[1] === 0x35))
98
+ return decodePnm(bytes);
99
+ return null;
100
+ }
101
+ function decodePnm(bytes) {
102
+ const fields = [];
103
+ let index = 2;
104
+ while (fields.length < 3 && index < bytes.length) {
105
+ while (index < bytes.length && /\s/.test(String.fromCharCode(bytes[index])))
106
+ index += 1;
107
+ if (bytes[index] === 0x23) {
108
+ while (index < bytes.length && bytes[index] !== 0x0a)
109
+ index += 1;
110
+ continue;
111
+ }
112
+ let field = "";
113
+ while (index < bytes.length && !/\s/.test(String.fromCharCode(bytes[index])))
114
+ field += String.fromCharCode(bytes[index++]);
115
+ fields.push(field);
116
+ }
117
+ const [width, height, maxValue] = fields.map(Number);
118
+ if (!width || !height || maxValue !== 255)
119
+ return null;
120
+ const channels = bytes[1] === 0x36 ? 3 : 1;
121
+ return { width, height, channels, data: bytes.subarray(index + 1, index + 1 + width * height * channels) };
122
+ }
123
+ /** zlib inflate with the realm's own `DecompressionStream`, in a page, a worker or Node. */
124
+ async function inflate(data) {
125
+ const globals = globalThis;
126
+ if (!globals.DecompressionStream || !globals.Blob || !globals.Response) {
127
+ throw new Error("cascade.assert.NotBlank: this host has no DecompressionStream to read a PNG with");
128
+ }
129
+ const stream = new globals.Blob([data]).stream().pipeThrough(new globals.DecompressionStream("deflate"));
130
+ return new Uint8Array(await new globals.Response(stream).arrayBuffer());
131
+ }
132
+ async function decodePng(bytes) {
133
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
134
+ let offset = 8;
135
+ let width = 0;
136
+ let height = 0;
137
+ let channels = 0;
138
+ const idat = [];
139
+ while (offset + 8 <= bytes.length) {
140
+ const length = view.getUint32(offset);
141
+ const type = String.fromCharCode(...bytes.subarray(offset + 4, offset + 8));
142
+ const body = bytes.subarray(offset + 8, offset + 8 + length);
143
+ if (type === "IHDR") {
144
+ width = view.getUint32(offset + 8);
145
+ height = view.getUint32(offset + 12);
146
+ const depth = body[8];
147
+ const colourType = body[9];
148
+ const interlace = body[12];
149
+ channels = { 0: 1, 4: 2, 2: 3, 6: 4 }[colourType] ?? 0;
150
+ if (depth !== 8 || !channels || interlace !== 0)
151
+ return null;
152
+ }
153
+ else if (type === "IDAT")
154
+ idat.push(body);
155
+ else if (type === "IEND")
156
+ break;
157
+ offset += 12 + length;
158
+ }
159
+ if (!width || !height || !idat.length)
160
+ return null;
161
+ const joined = new Uint8Array(idat.reduce((sum, part) => sum + part.length, 0));
162
+ let at = 0;
163
+ for (const part of idat) {
164
+ joined.set(part, at);
165
+ at += part.length;
166
+ }
167
+ const raw = await inflate(joined);
168
+ const stride = width * channels;
169
+ const data = new Uint8Array(stride * height);
170
+ for (let row = 0; row < height; row += 1) {
171
+ const filter = raw[row * (stride + 1)];
172
+ const line = raw.subarray(row * (stride + 1) + 1, (row + 1) * (stride + 1));
173
+ const out = data.subarray(row * stride, (row + 1) * stride);
174
+ const previous = row > 0 ? data.subarray((row - 1) * stride, row * stride) : new Uint8Array(stride);
175
+ for (let x = 0; x < stride; x += 1) {
176
+ const left = x >= channels ? out[x - channels] : 0;
177
+ const up = previous[x];
178
+ const upLeft = x >= channels ? previous[x - channels] : 0;
179
+ const value = line[x];
180
+ switch (filter) {
181
+ case 0:
182
+ out[x] = value;
183
+ break;
184
+ case 1:
185
+ out[x] = (value + left) & 255;
186
+ break;
187
+ case 2:
188
+ out[x] = (value + up) & 255;
189
+ break;
190
+ case 3:
191
+ out[x] = (value + ((left + up) >> 1)) & 255;
192
+ break;
193
+ case 4: {
194
+ const estimate = left + up - upLeft;
195
+ const a = Math.abs(estimate - left);
196
+ const b = Math.abs(estimate - up);
197
+ const c = Math.abs(estimate - upLeft);
198
+ out[x] = (value + (a <= b && a <= c ? left : b <= c ? up : upLeft)) & 255;
199
+ break;
200
+ }
201
+ default: return null;
202
+ }
203
+ }
204
+ }
205
+ return { width, height, channels, data };
206
+ }
207
+ //# sourceMappingURL=checks.js.map
@@ -0,0 +1,130 @@
1
+ /** The declarations of `cascade.assert.*`; see `index.ts`. */
2
+ export declare const rangeDefinition: {
3
+ readonly apiVersion: 1;
4
+ readonly runsOn: "portable";
5
+ readonly label: "Assert Range";
6
+ readonly description: "Fails the run unless value is a finite number in [min, max]; passes value through";
7
+ readonly icon: "ShieldCheck";
8
+ readonly inputs: {
9
+ readonly value: {
10
+ readonly kind: "data";
11
+ readonly type: "float";
12
+ readonly default: 0;
13
+ };
14
+ };
15
+ readonly outputs: {
16
+ readonly value: {
17
+ readonly kind: "data";
18
+ readonly type: "float";
19
+ };
20
+ };
21
+ readonly props: {
22
+ readonly min: {
23
+ readonly type: "float";
24
+ readonly default: 0;
25
+ };
26
+ readonly max: {
27
+ readonly type: "float";
28
+ readonly default: 1;
29
+ };
30
+ readonly message: {
31
+ readonly type: "string";
32
+ readonly default: "";
33
+ };
34
+ };
35
+ };
36
+ export declare const equalsDefinition: {
37
+ readonly apiVersion: 1;
38
+ readonly runsOn: "portable";
39
+ readonly label: "Assert Equals";
40
+ readonly description: "Fails the run unless value equals expected (JSON-equal; numbers within tolerance); passes value through";
41
+ readonly icon: "ShieldCheck";
42
+ readonly inputs: {
43
+ readonly value: {
44
+ readonly kind: "data";
45
+ readonly type: "any";
46
+ readonly default: null;
47
+ };
48
+ };
49
+ readonly outputs: {
50
+ readonly value: {
51
+ readonly kind: "data";
52
+ readonly type: "any";
53
+ };
54
+ };
55
+ readonly props: {
56
+ readonly expected: {
57
+ readonly type: "any";
58
+ readonly default: null;
59
+ };
60
+ readonly tolerance: {
61
+ readonly type: "float";
62
+ readonly default: 0;
63
+ readonly min: 0;
64
+ };
65
+ readonly message: {
66
+ readonly type: "string";
67
+ readonly default: "";
68
+ };
69
+ };
70
+ };
71
+ export declare const trueDefinition: {
72
+ readonly apiVersion: 1;
73
+ readonly runsOn: "portable";
74
+ readonly label: "Assert True";
75
+ readonly description: "Fails the run unless value is true (wire a condition, or give the prop an expression); passes value through";
76
+ readonly icon: "ShieldCheck";
77
+ readonly inputs: {
78
+ readonly value: {
79
+ readonly kind: "data";
80
+ readonly type: "bool";
81
+ readonly default: false;
82
+ };
83
+ };
84
+ readonly outputs: {
85
+ readonly value: {
86
+ readonly kind: "data";
87
+ readonly type: "bool";
88
+ };
89
+ };
90
+ readonly props: {
91
+ readonly message: {
92
+ readonly type: "string";
93
+ readonly default: "";
94
+ };
95
+ };
96
+ };
97
+ export declare const notBlankDefinition: {
98
+ readonly apiVersion: 1;
99
+ readonly runsOn: "portable";
100
+ readonly label: "Assert Not Blank";
101
+ readonly description: "Fails the run when an image (PNG or PPM) is one flat colour or fully transparent; passes the image through";
102
+ readonly icon: "ShieldCheck";
103
+ readonly capabilities: readonly ["assets"];
104
+ readonly inputs: {
105
+ readonly image: {
106
+ readonly kind: "data";
107
+ readonly type: "image";
108
+ };
109
+ };
110
+ readonly outputs: {
111
+ readonly image: {
112
+ readonly kind: "data";
113
+ readonly type: "image";
114
+ };
115
+ };
116
+ readonly props: {
117
+ readonly tolerance: {
118
+ readonly type: "int";
119
+ readonly default: 0;
120
+ readonly min: 0;
121
+ readonly max: 255;
122
+ readonly step: 1;
123
+ };
124
+ readonly message: {
125
+ readonly type: "string";
126
+ readonly default: "";
127
+ };
128
+ };
129
+ };
130
+ //# sourceMappingURL=definitions.d.ts.map
@@ -0,0 +1,54 @@
1
+ /** The declarations of `cascade.assert.*`; see `index.ts`. */
2
+ export const rangeDefinition = {
3
+ apiVersion: 1,
4
+ runsOn: "portable",
5
+ label: "Assert Range",
6
+ description: "Fails the run unless value is a finite number in [min, max]; passes value through",
7
+ icon: "ShieldCheck",
8
+ inputs: { value: { kind: "data", type: "float", default: 0 } },
9
+ outputs: { value: { kind: "data", type: "float" } },
10
+ props: {
11
+ min: { type: "float", default: 0 },
12
+ max: { type: "float", default: 1 },
13
+ message: { type: "string", default: "" },
14
+ },
15
+ };
16
+ export const equalsDefinition = {
17
+ apiVersion: 1,
18
+ runsOn: "portable",
19
+ label: "Assert Equals",
20
+ description: "Fails the run unless value equals expected (JSON-equal; numbers within tolerance); passes value through",
21
+ icon: "ShieldCheck",
22
+ inputs: { value: { kind: "data", type: "any", default: null } },
23
+ outputs: { value: { kind: "data", type: "any" } },
24
+ props: {
25
+ expected: { type: "any", default: null },
26
+ tolerance: { type: "float", default: 0, min: 0 },
27
+ message: { type: "string", default: "" },
28
+ },
29
+ };
30
+ export const trueDefinition = {
31
+ apiVersion: 1,
32
+ runsOn: "portable",
33
+ label: "Assert True",
34
+ description: "Fails the run unless value is true (wire a condition, or give the prop an expression); passes value through",
35
+ icon: "ShieldCheck",
36
+ inputs: { value: { kind: "data", type: "bool", default: false } },
37
+ outputs: { value: { kind: "data", type: "bool" } },
38
+ props: { message: { type: "string", default: "" } },
39
+ };
40
+ export const notBlankDefinition = {
41
+ apiVersion: 1,
42
+ runsOn: "portable",
43
+ label: "Assert Not Blank",
44
+ description: "Fails the run when an image (PNG or PPM) is one flat colour or fully transparent; passes the image through",
45
+ icon: "ShieldCheck",
46
+ capabilities: ["assets"],
47
+ inputs: { image: { kind: "data", type: "image" } },
48
+ outputs: { image: { kind: "data", type: "image" } },
49
+ props: {
50
+ tolerance: { type: "int", default: 0, min: 0, max: 255, step: 1 },
51
+ message: { type: "string", default: "" },
52
+ },
53
+ };
54
+ //# sourceMappingURL=definitions.js.map
@@ -0,0 +1,152 @@
1
+ import type { DefinitionNodeRegistration } from "../../types.js";
2
+ /**
3
+ * `cascade.assert.*`: nodes that fail a run.
4
+ *
5
+ * An agent checking its own render used to do it outside the graph (a QR code
6
+ * scanned by a separate script, a blank frame noticed by eye). An assertion
7
+ * node puts the check in the graph: when it does not hold, the node throws,
8
+ * the run fails with a non-zero exit, and `run --json` carries the message,
9
+ * `{ ok: false, error: { message: "Assertion failed in \"…\" …" } }`.
10
+ *
11
+ * Each passes its input through, so an assertion can sit inline and is cooked
12
+ * whenever what is downstream of it is. An unconsumed `NotBlank` is not saved
13
+ * as an image output (`src/cli/imageOutputs.ts` skips this namespace).
14
+ *
15
+ * Portable since 2026-11-07, so Studio registers them too: `NotBlank` takes the
16
+ * host's in-memory pixels when it has them and otherwise decodes the PNG (or
17
+ * PPM) with the realm's `DecompressionStream` rather than `node:zlib`. The
18
+ * declarations are in `definitions.ts` and the bodies in `checks.ts`, behind a
19
+ * dynamic import, because this list is in Studio's first-paint bundle.
20
+ */
21
+ export declare const ASSERT_NAMESPACE = "cascade.assert.";
22
+ export * from "./definitions.js";
23
+ export declare const rangeRegistration: DefinitionNodeRegistration<{
24
+ readonly apiVersion: 1;
25
+ readonly runsOn: "portable";
26
+ readonly label: "Assert Range";
27
+ readonly description: "Fails the run unless value is a finite number in [min, max]; passes value through";
28
+ readonly icon: "ShieldCheck";
29
+ readonly inputs: {
30
+ readonly value: {
31
+ readonly kind: "data";
32
+ readonly type: "float";
33
+ readonly default: 0;
34
+ };
35
+ };
36
+ readonly outputs: {
37
+ readonly value: {
38
+ readonly kind: "data";
39
+ readonly type: "float";
40
+ };
41
+ };
42
+ readonly props: {
43
+ readonly min: {
44
+ readonly type: "float";
45
+ readonly default: 0;
46
+ };
47
+ readonly max: {
48
+ readonly type: "float";
49
+ readonly default: 1;
50
+ };
51
+ readonly message: {
52
+ readonly type: "string";
53
+ readonly default: "";
54
+ };
55
+ };
56
+ }>;
57
+ export declare const equalsRegistration: DefinitionNodeRegistration<{
58
+ readonly apiVersion: 1;
59
+ readonly runsOn: "portable";
60
+ readonly label: "Assert Equals";
61
+ readonly description: "Fails the run unless value equals expected (JSON-equal; numbers within tolerance); passes value through";
62
+ readonly icon: "ShieldCheck";
63
+ readonly inputs: {
64
+ readonly value: {
65
+ readonly kind: "data";
66
+ readonly type: "any";
67
+ readonly default: null;
68
+ };
69
+ };
70
+ readonly outputs: {
71
+ readonly value: {
72
+ readonly kind: "data";
73
+ readonly type: "any";
74
+ };
75
+ };
76
+ readonly props: {
77
+ readonly expected: {
78
+ readonly type: "any";
79
+ readonly default: null;
80
+ };
81
+ readonly tolerance: {
82
+ readonly type: "float";
83
+ readonly default: 0;
84
+ readonly min: 0;
85
+ };
86
+ readonly message: {
87
+ readonly type: "string";
88
+ readonly default: "";
89
+ };
90
+ };
91
+ }>;
92
+ export declare const trueRegistration: DefinitionNodeRegistration<{
93
+ readonly apiVersion: 1;
94
+ readonly runsOn: "portable";
95
+ readonly label: "Assert True";
96
+ readonly description: "Fails the run unless value is true (wire a condition, or give the prop an expression); passes value through";
97
+ readonly icon: "ShieldCheck";
98
+ readonly inputs: {
99
+ readonly value: {
100
+ readonly kind: "data";
101
+ readonly type: "bool";
102
+ readonly default: false;
103
+ };
104
+ };
105
+ readonly outputs: {
106
+ readonly value: {
107
+ readonly kind: "data";
108
+ readonly type: "bool";
109
+ };
110
+ };
111
+ readonly props: {
112
+ readonly message: {
113
+ readonly type: "string";
114
+ readonly default: "";
115
+ };
116
+ };
117
+ }>;
118
+ export declare const notBlankRegistration: DefinitionNodeRegistration<{
119
+ readonly apiVersion: 1;
120
+ readonly runsOn: "portable";
121
+ readonly label: "Assert Not Blank";
122
+ readonly description: "Fails the run when an image (PNG or PPM) is one flat colour or fully transparent; passes the image through";
123
+ readonly icon: "ShieldCheck";
124
+ readonly capabilities: readonly ["assets"];
125
+ readonly inputs: {
126
+ readonly image: {
127
+ readonly kind: "data";
128
+ readonly type: "image";
129
+ };
130
+ };
131
+ readonly outputs: {
132
+ readonly image: {
133
+ readonly kind: "data";
134
+ readonly type: "image";
135
+ };
136
+ };
137
+ readonly props: {
138
+ readonly tolerance: {
139
+ readonly type: "int";
140
+ readonly default: 0;
141
+ readonly min: 0;
142
+ readonly max: 255;
143
+ readonly step: 1;
144
+ };
145
+ readonly message: {
146
+ readonly type: "string";
147
+ readonly default: "";
148
+ };
149
+ };
150
+ }>;
151
+ export declare const assertNodeRegistrations: readonly DefinitionNodeRegistration[];
152
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,36 @@
1
+ import { equalsDefinition, notBlankDefinition, rangeDefinition, trueDefinition } from "./definitions.js";
2
+ /**
3
+ * `cascade.assert.*`: nodes that fail a run.
4
+ *
5
+ * An agent checking its own render used to do it outside the graph (a QR code
6
+ * scanned by a separate script, a blank frame noticed by eye). An assertion
7
+ * node puts the check in the graph: when it does not hold, the node throws,
8
+ * the run fails with a non-zero exit, and `run --json` carries the message,
9
+ * `{ ok: false, error: { message: "Assertion failed in \"…\" …" } }`.
10
+ *
11
+ * Each passes its input through, so an assertion can sit inline and is cooked
12
+ * whenever what is downstream of it is. An unconsumed `NotBlank` is not saved
13
+ * as an image output (`src/cli/imageOutputs.ts` skips this namespace).
14
+ *
15
+ * Portable since 2026-11-07, so Studio registers them too: `NotBlank` takes the
16
+ * host's in-memory pixels when it has them and otherwise decodes the PNG (or
17
+ * PPM) with the realm's `DecompressionStream` rather than `node:zlib`. The
18
+ * declarations are in `definitions.ts` and the bodies in `checks.ts`, behind a
19
+ * dynamic import, because this list is in Studio's first-paint bundle.
20
+ */
21
+ export const ASSERT_NAMESPACE = "cascade.assert.";
22
+ export * from "./definitions.js";
23
+ function lazy(name, definition, load) {
24
+ return { kind: "definition-v1", moduleId: `${ASSERT_NAMESPACE}${name}`, definition, loadExecute: load };
25
+ }
26
+ export const rangeRegistration = lazy("Range", rangeDefinition, async () => (await import("./checks.js")).executeRange);
27
+ export const equalsRegistration = lazy("Equals", equalsDefinition, async () => (await import("./checks.js")).executeEquals);
28
+ export const trueRegistration = lazy("True", trueDefinition, async () => (await import("./checks.js")).executeTrue);
29
+ export const notBlankRegistration = lazy("NotBlank", notBlankDefinition, async () => (await import("./checks.js")).executeNotBlank);
30
+ export const assertNodeRegistrations = Object.freeze([
31
+ equalsRegistration,
32
+ notBlankRegistration,
33
+ rangeRegistration,
34
+ trueRegistration,
35
+ ]);
36
+ //# sourceMappingURL=index.js.map