@field/cascade 0.10.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 (229) hide show
  1. package/AGENTS.md +25 -5
  2. package/ARCHITECTURE.md +5 -4
  3. package/CHANGELOG.md +382 -0
  4. package/README.md +1 -2
  5. package/dist/assets/AgentPanel-1JugFjE4.css +1 -0
  6. package/dist/assets/AgentPanel-BDL5MddA.js +5 -0
  7. package/dist/assets/{CodeEditor-DSZBpzdy.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-p25h3tj0.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-DzFLUotB.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-BICrEt0y.js → core-DB-MkGjj.js} +3 -4
  28. package/dist/assets/coreTypes-DtnDATZ7.js +1 -0
  29. package/dist/assets/{cssMode-D7TXw57I.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-DvkCO3ko.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-BA2lxsF1.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-BcDiyPd8.js → handlebars-CcD3aMeL.js} +1 -1
  41. package/dist/assets/{html-C7O4TyhX.js → html-DvZWGfqb.js} +1 -1
  42. package/dist/assets/{htmlMode-WaDGF3Mj.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-CpIEK7tz.js → javascript-Cap2ahgh.js} +1 -1
  49. package/dist/assets/{jsonMode-B2FltW0f.js → jsonMode-B0uLjp9C.js} +1 -1
  50. package/dist/assets/{liquid-CN6FFHZE.js → liquid-CnxwhfLn.js} +1 -1
  51. package/dist/assets/{lspLanguageFeatures-QhV9XtLp.js → lspLanguageFeatures-Dn9hlKX6.js} +1 -1
  52. package/dist/assets/math-GeULS-sk.js +1 -0
  53. package/dist/assets/{mdx-DE89pafV.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-ByTllTB9.js → python-CGyFk4XK.js} +1 -1
  57. package/dist/assets/{razor-USYlRRCE.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-BXpN8iUz.js → tsMode-BOeXzO9H.js} +1 -1
  64. package/dist/assets/{typescript-DInBjGQQ.js → typescript-J1SnE_Tu.js} +1 -1
  65. package/dist/assets/{workers-C7Ye5B7D.js → workers-CYwwTiVF.js} +1 -1
  66. package/dist/assets/{xml-U0Uocqyo.js → xml-DnPRNzK9.js} +1 -1
  67. package/dist/assets/{yaml-CBw_PVYf.js → yaml-BmmYBNa9.js} +1 -1
  68. package/dist/cli/DefinitionNode-P7AA4TAU.js +15 -0
  69. package/dist/cli/Graph-OB6EWSGL.js +2975 -0
  70. package/dist/cli/assert-GROGQNCQ.js +25 -0
  71. package/dist/cli/checks-3WB44XBL.js +189 -0
  72. package/dist/cli/chunk-2JEU3MGB.js +1125 -0
  73. package/dist/cli/chunk-2KXB47FP.js +115 -0
  74. package/dist/cli/chunk-3RXL5GHB.js +816 -0
  75. package/dist/cli/chunk-53LGFPWX.js +138 -0
  76. package/dist/cli/chunk-5ORUQ6ES.js +267 -0
  77. package/dist/cli/chunk-73S4JZXK.js +336 -0
  78. package/dist/cli/chunk-7QE2ZNSN.js +121 -0
  79. package/dist/cli/chunk-BP7UKF36.js +376 -0
  80. package/dist/cli/chunk-BPODRH52.js +201 -0
  81. package/dist/cli/chunk-BZ63XQ3I.js +1301 -0
  82. package/dist/cli/chunk-CKS2FQWR.js +89 -0
  83. package/dist/cli/chunk-CO22XHNG.js +593 -0
  84. package/dist/cli/chunk-CTPLRSWF.js +2197 -0
  85. package/dist/cli/chunk-DEQSEFFH.js +153 -0
  86. package/dist/cli/chunk-EU4ZFZ7Y.js +122 -0
  87. package/dist/cli/chunk-EZRWJ4G2.js +104 -0
  88. package/dist/cli/chunk-FWMSGKEH.js +71 -0
  89. package/dist/cli/chunk-G23A2BZZ.js +705 -0
  90. package/dist/cli/chunk-GRIN2DDP.js +1465 -0
  91. package/dist/cli/chunk-HFHWYL55.js +139 -0
  92. package/dist/cli/chunk-HSNOBIVT.js +263 -0
  93. package/dist/cli/chunk-ICH7N5PN.js +26 -0
  94. package/dist/cli/chunk-J7VK7MNF.js +418 -0
  95. package/dist/cli/chunk-JVOCK5W4.js +83 -0
  96. package/dist/cli/chunk-KBGUO27Q.js +15 -0
  97. package/dist/cli/chunk-KH4MUBKF.js +1551 -0
  98. package/dist/cli/chunk-L6B6FUQP.js +330 -0
  99. package/dist/cli/chunk-LTXWBXOM.js +438 -0
  100. package/dist/cli/chunk-MI5JLQYA.js +220 -0
  101. package/dist/cli/chunk-MP653A5O.js +3766 -0
  102. package/dist/cli/chunk-NJFMDXHR.js +320 -0
  103. package/dist/cli/chunk-OM6BEME6.js +267 -0
  104. package/dist/cli/chunk-OWWBYLHC.js +136 -0
  105. package/dist/cli/chunk-QQPA2ZHH.js +273 -0
  106. package/dist/cli/chunk-RUU3YWXU.js +99 -0
  107. package/dist/cli/chunk-RXDIMXN5.js +98 -0
  108. package/dist/cli/chunk-ST6LROUG.js +267 -0
  109. package/dist/cli/chunk-TJW67QSI.js +225 -0
  110. package/dist/cli/chunk-WD62JIP6.js +118 -0
  111. package/dist/cli/chunk-WGKPJZDY.js +364 -0
  112. package/dist/cli/chunk-X4WKAOQP.js +634 -0
  113. package/dist/cli/chunk-X566FWBV.js +156 -0
  114. package/dist/cli/chunk-YH637PDL.js +317 -0
  115. package/dist/cli/chunk-ZL4JTG5Y.js +37 -0
  116. package/dist/cli/chunk-ZSCUZE6I.js +112 -0
  117. package/dist/cli/cliCommands-JJN5Z3ZC.js +3470 -0
  118. package/dist/cli/commands-PHYFYA3F.js +1374 -0
  119. package/dist/cli/contactSheet-2PQVRUFW.js +11 -0
  120. package/dist/cli/definitions-BPOPW52L.js +32 -0
  121. package/dist/cli/doctorCommand-ORWQRNYP.js +106 -0
  122. package/dist/cli/edit-S5KEQ5RA.js +28 -0
  123. package/dist/cli/editorCommands-NFR32JOY.js +41 -0
  124. package/dist/cli/encode-ICPEPMH5.js +17 -0
  125. package/dist/cli/file-3HTQHPYQ.js +41 -0
  126. package/dist/cli/filters-6ODFWYK4.js +379 -0
  127. package/dist/cli/frames-LSAUBZ7P.js +16 -0
  128. package/dist/cli/generators-YU7GB4MD.js +210 -0
  129. package/dist/cli/geo-6ZPGKZRU.js +43 -0
  130. package/dist/cli/graphCommands-EOUOBOIJ.js +184 -0
  131. package/dist/cli/graphFile-YTGJB5F5.js +292 -0
  132. package/dist/cli/headlessCanvas-ENRHC7GS.js +16 -0
  133. package/dist/cli/helpCommand-PULH6ZYB.js +157 -0
  134. package/dist/cli/image-TXELZZK5.js +55 -0
  135. package/dist/cli/index.js +100 -30756
  136. package/dist/cli/math-RSYRQ3C6.js +16 -0
  137. package/dist/cli/mcp-JVZFQOEX.js +164 -0
  138. package/dist/cli/parity-X6DEH2DV.js +334 -0
  139. package/dist/cli/pop-B7SIIYKN.js +39 -0
  140. package/dist/cli/project-VYGIA7JX.js +12 -0
  141. package/dist/cli/projectDefinition-F3D7UIHJ.js +27 -0
  142. package/dist/cli/projectRuntime-2Q4JKEH3.js +55 -0
  143. package/dist/cli/registerStandardNodes-BXJCB6E4.js +744 -0
  144. package/dist/cli/render-PT4MWI6U.js +438 -0
  145. package/dist/cli/runCommand-HNPQVZMS.js +198 -0
  146. package/dist/cli/runner-5XPXVW7J.js +43 -0
  147. package/dist/cli/runtime/image.ts +302 -0
  148. package/dist/cli/runtime/io.ts +78 -0
  149. package/dist/cli/runtime/stage.ts +4 -1
  150. package/dist/cli/share-MHF6TNRK.js +166 -0
  151. package/dist/cli/stageCache-TY2BFZJR.js +15 -0
  152. package/dist/cli/stageRunner-KY2EJP7X.js +10 -0
  153. package/dist/cli/text-MB3EKR4H.js +126 -0
  154. package/dist/cli/verbs-PI2JH5FI.js +19 -0
  155. package/dist/cli/webBuild-WPPMGGW7.js +44 -0
  156. package/dist/contracts/capabilities.d.ts +18 -1
  157. package/dist/contracts/color.d.ts +8 -0
  158. package/dist/contracts/color.js +23 -0
  159. package/dist/contracts/definition.d.ts +10 -0
  160. package/dist/contracts/documentFormat.d.ts +18 -0
  161. package/dist/contracts/documentFormat.js +195 -0
  162. package/dist/contracts/editor.d.ts +17 -0
  163. package/dist/contracts/index.d.ts +1 -0
  164. package/dist/contracts/index.js +1 -0
  165. package/dist/contracts/schema.d.ts +1 -1
  166. package/dist/contracts/schema.js +1 -0
  167. package/dist/contracts/validate.js +11 -1
  168. package/dist/contracts/values.d.ts +24 -0
  169. package/dist/image.d.ts +156 -0
  170. package/dist/image.js +161 -0
  171. package/dist/index.html +8 -2
  172. package/dist/io.d.ts +29 -0
  173. package/dist/io.js +40 -0
  174. package/dist/player/app.js +3 -2
  175. package/dist/player/editor.js +35 -9
  176. package/dist/player/editorEngine.js +3 -2
  177. package/dist/runtime/builtins/assert/checks.d.ts +18 -0
  178. package/dist/runtime/builtins/assert/checks.js +207 -0
  179. package/dist/runtime/builtins/assert/definitions.d.ts +130 -0
  180. package/dist/runtime/builtins/assert/definitions.js +54 -0
  181. package/dist/runtime/builtins/assert/index.d.ts +152 -0
  182. package/dist/runtime/builtins/assert/index.js +36 -0
  183. package/dist/runtime/builtins/image/contactSheet.d.ts +64 -0
  184. package/dist/runtime/builtins/image/contactSheet.js +150 -0
  185. package/dist/runtime/builtins/image/definitions.d.ts +950 -0
  186. package/dist/runtime/builtins/image/definitions.js +343 -0
  187. package/dist/runtime/builtins/image/file.d.ts +8 -0
  188. package/dist/runtime/builtins/image/file.js +37 -0
  189. package/dist/runtime/builtins/image/filters.d.ts +8 -0
  190. package/dist/runtime/builtins/image/filters.js +394 -0
  191. package/dist/runtime/builtins/image/generators.d.ts +7 -0
  192. package/dist/runtime/builtins/image/generators.js +144 -0
  193. package/dist/runtime/builtins/image/index.d.ts +952 -0
  194. package/dist/runtime/builtins/image/index.js +44 -0
  195. package/dist/runtime/builtins/image/io.d.ts +100 -0
  196. package/dist/runtime/builtins/image/io.js +142 -0
  197. package/dist/runtime/builtins/image/planar.d.ts +55 -0
  198. package/dist/runtime/builtins/image/planar.js +274 -0
  199. package/dist/runtime/builtins/image/simplex.d.ts +22 -0
  200. package/dist/runtime/builtins/image/simplex.js +92 -0
  201. package/dist/runtime/builtins/image/text.d.ts +4 -0
  202. package/dist/runtime/builtins/image/text.js +141 -0
  203. package/dist/runtime/builtins/index.d.ts +3 -0
  204. package/dist/runtime/builtins/index.js +7 -0
  205. package/dist/runtime/gpu.d.ts +25 -0
  206. package/dist/runtime/gpu.js +40 -0
  207. package/dist/runtime/runtime.d.ts +2 -0
  208. package/dist/runtime/runtime.js +268 -44
  209. package/dist/runtime/types.d.ts +30 -0
  210. package/dist/stage.js +4 -1
  211. package/doc/CLI_REFERENCE.md +91 -2
  212. package/doc/EDITOR_EXPORT.md +37 -2
  213. package/doc/MCP.md +18 -1
  214. package/doc/NODE_REFERENCE.md +424 -10
  215. package/doc/PROJECT_AUTHORING.md +222 -7
  216. package/package.json +19 -4
  217. package/dist/assets/CodePanel-sY6sZa7B.js +0 -2
  218. package/dist/assets/CustomNodeDialog-BRKnqZhp.js +0 -1
  219. package/dist/assets/ExportDialog-C7dVGua4.js +0 -358
  220. package/dist/assets/ExportDialog-DOq0flMV.css +0 -1
  221. package/dist/assets/OpenGraphDialog-ZsgL-iwr.js +0 -1
  222. package/dist/assets/ProjectSettings-Cw17nNe3.js +0 -2
  223. package/dist/assets/definitions-DUww2Rt-.js +0 -1
  224. package/dist/assets/geo-C2RHOVHC.js +0 -1
  225. package/dist/assets/image-DR4oeyJB.js +0 -2062
  226. package/dist/assets/index-7vae6YFC.css +0 -1
  227. package/dist/assets/index-DyvDC6pS.js +0 -219
  228. package/dist/assets/math-DaDEgWKt.js +0 -1
  229. package/dist/assets/pop-pLH7L0PW.js +0 -1
@@ -0,0 +1,376 @@
1
+ import {
2
+ expressionEngine
3
+ } from "./chunk-RUU3YWXU.js";
4
+ import {
5
+ imagePath,
6
+ sequenceFileName,
7
+ sequenceWidth
8
+ } from "./chunk-ICH7N5PN.js";
9
+ import {
10
+ TimeState,
11
+ runFrameRange
12
+ } from "./chunk-CO22XHNG.js";
13
+
14
+ // src/cli/frames.ts
15
+ import fs from "node:fs/promises";
16
+ import path from "node:path";
17
+
18
+ // src/engine/cascade.ts
19
+ var CascadeContext = class {
20
+ constructor() {
21
+ this._graph = null;
22
+ this._pwd = null;
23
+ /**
24
+ * Time state. Frames are stored as real numbers: `frame()` reports the
25
+ * integer frame ($F) and `fframe()` the fractional one ($FF), so sub-frame
26
+ * time exists rather than being floored away at the door.
27
+ */
28
+ this._time = new TimeState();
29
+ this._selectedNodes = /* @__PURE__ */ new Set();
30
+ }
31
+ /**
32
+ * Initialize with a graph reference
33
+ */
34
+ setGraph(graph) {
35
+ this._graph = graph;
36
+ expressionEngine.setGraph(graph);
37
+ }
38
+ /**
39
+ * Get the current graph
40
+ */
41
+ getGraph() {
42
+ return this._graph;
43
+ }
44
+ // ============ Node Access ============
45
+ /**
46
+ * Find a node by path (absolute or relative to pwd)
47
+ */
48
+ node(path2) {
49
+ if (!this._graph) return null;
50
+ if (path2.startsWith("/")) {
51
+ return this.resolveAbsolutePath(path2);
52
+ }
53
+ if (this._pwd) {
54
+ return this._pwd.node(path2);
55
+ }
56
+ return this.resolveAbsolutePath("/" + path2);
57
+ }
58
+ /**
59
+ * Find a parameter by path (e.g., "/node1/radius")
60
+ */
61
+ parm(path2) {
62
+ const lastSlash = path2.lastIndexOf("/");
63
+ if (lastSlash === -1) return null;
64
+ const nodePath = path2.substring(0, lastSlash) || "/";
65
+ const parmName = path2.substring(lastSlash + 1);
66
+ const node = this.node(nodePath);
67
+ return node?.parm(parmName) ?? null;
68
+ }
69
+ /**
70
+ * Get the root node (virtual - returns null, represents "/")
71
+ * In Cascade, root is not a real node but the graph itself
72
+ */
73
+ root() {
74
+ return null;
75
+ }
76
+ /**
77
+ * Get current working directory/network
78
+ */
79
+ pwd() {
80
+ return this._pwd;
81
+ }
82
+ /**
83
+ * Set current working directory/network
84
+ */
85
+ setPwd(node) {
86
+ this._pwd = node;
87
+ }
88
+ /**
89
+ * Navigate to a path (like cd command)
90
+ */
91
+ cd(path2) {
92
+ if (path2 === "/") {
93
+ this._pwd = null;
94
+ return;
95
+ }
96
+ const target = this.node(path2);
97
+ if (target && target.isNetwork()) {
98
+ this._pwd = target;
99
+ }
100
+ }
101
+ // ============ Selection ============
102
+ /**
103
+ * Get all selected nodes
104
+ */
105
+ selectedNodes() {
106
+ if (!this._graph) return [];
107
+ return Array.from(this._selectedNodes).map((id) => this._graph.getNode(id)).filter((n) => n !== null);
108
+ }
109
+ /**
110
+ * Get selected node IDs
111
+ */
112
+ selectedNodeIds() {
113
+ return Array.from(this._selectedNodes);
114
+ }
115
+ /**
116
+ * Set selection (internal use by editor)
117
+ */
118
+ setSelection(nodeIds) {
119
+ this._selectedNodes = new Set(nodeIds);
120
+ this.onSelectionChange?.(nodeIds);
121
+ }
122
+ /**
123
+ * Clear all selection
124
+ */
125
+ clearAllSelected() {
126
+ this._selectedNodes.clear();
127
+ this.onSelectionChange?.([]);
128
+ }
129
+ // ============ Time/Playback ============
130
+ /**
131
+ * Get current frame number (integer) — $F
132
+ */
133
+ frame() {
134
+ return this._time.frame;
135
+ }
136
+ /**
137
+ * Get current frame as a fractional value — $FF
138
+ */
139
+ fframe() {
140
+ return this._time.frameFraction;
141
+ }
142
+ /**
143
+ * Get current time in seconds — $T
144
+ */
145
+ time() {
146
+ return this._time.time;
147
+ }
148
+ /**
149
+ * Get frames per second — $FPS
150
+ */
151
+ fps() {
152
+ return this._time.fps;
153
+ }
154
+ /**
155
+ * Set current frame. Fractional frames are preserved; `frame()` still
156
+ * reports the floored integer frame.
157
+ */
158
+ setFrame(frame) {
159
+ this._time.setFrame(frame);
160
+ expressionEngine.setFrame(this._time.frameFraction);
161
+ }
162
+ /**
163
+ * Set current time in seconds, keeping the sub-frame remainder
164
+ */
165
+ setTime(time) {
166
+ this._time.setTime(time);
167
+ expressionEngine.setFrame(this._time.frameFraction);
168
+ }
169
+ /**
170
+ * Set FPS
171
+ */
172
+ setFps(fps) {
173
+ this._time.setFps(fps);
174
+ expressionEngine.setFps(this._time.fps);
175
+ }
176
+ /**
177
+ * The clock a frame-range loop drives. Nothing here reads a wall clock —
178
+ * the caller owns time, which is what keeps a cook deterministic.
179
+ */
180
+ clock() {
181
+ return {
182
+ setFrame: (frame) => this.setFrame(frame),
183
+ markTimeDependentDirty: () => this.markTimeDependentDirty(),
184
+ getFrame: () => this.frame(),
185
+ getFrameFraction: () => this.fframe(),
186
+ getTime: () => this.time(),
187
+ getFps: () => this.fps()
188
+ };
189
+ }
190
+ // ============ Channel Shortcuts ============
191
+ /**
192
+ * Get parameter value as number (shorthand)
193
+ */
194
+ ch(path2) {
195
+ const p = this.parm(path2);
196
+ return p?.evalAsFloat() ?? 0;
197
+ }
198
+ /**
199
+ * Get parameter value as string (shorthand)
200
+ */
201
+ chs(path2) {
202
+ const p = this.parm(path2);
203
+ return p?.evalAsString() ?? "";
204
+ }
205
+ /**
206
+ * Get parameter value as vector (shorthand)
207
+ */
208
+ chv(path2) {
209
+ const p = this.parm(path2);
210
+ const val = p?.eval();
211
+ if (Array.isArray(val)) return val;
212
+ if (typeof val === "number") return [val];
213
+ return [];
214
+ }
215
+ // ============ Time-Dependent Updates ============
216
+ /**
217
+ * Mark all time-dependent nodes as dirty
218
+ * Call this at the start of each frame in the animation loop
219
+ */
220
+ markTimeDependentDirty() {
221
+ if (!this._graph) return;
222
+ for (const node of this._graph.nodes) {
223
+ if (node.isTimeDependent) {
224
+ node.markDirty();
225
+ }
226
+ }
227
+ }
228
+ /**
229
+ * Get all time-dependent nodes in the graph
230
+ */
231
+ getTimeDependentNodes() {
232
+ if (!this._graph) return [];
233
+ return this._graph.nodes.filter((n) => n.isTimeDependent);
234
+ }
235
+ /**
236
+ * Advance the frame and mark time-dependent nodes dirty.
237
+ * Convenience method for animation loops; `step` may be fractional.
238
+ * Returns the new fractional frame.
239
+ */
240
+ nextFrame(step = 1) {
241
+ this.setFrame(this.fframe() + step);
242
+ this.markTimeDependentDirty();
243
+ return this.fframe();
244
+ }
245
+ // ============ Internal Helpers ============
246
+ resolveAbsolutePath(path2) {
247
+ if (!this._graph) return null;
248
+ const segments = path2.replace(/^\//, "").split("/").filter((s) => s.length > 0);
249
+ if (segments.length === 0) {
250
+ return null;
251
+ }
252
+ let current = null;
253
+ const rootNodes = this._graph.nodes.filter((n) => !n.parent);
254
+ for (let i = 0; i < segments.length; i++) {
255
+ const segment = segments[i];
256
+ const searchIn = current ? current.children() : rootNodes;
257
+ const found = searchIn.find((n) => n.id === segment);
258
+ if (!found) return null;
259
+ current = found;
260
+ }
261
+ return current;
262
+ }
263
+ };
264
+ var cascade = new CascadeContext();
265
+
266
+ // src/engine/animation/frameRange.ts
267
+ async function evaluateFrameRange(options) {
268
+ const { fps, context = cascade, ...rest } = options;
269
+ const previousFps = context.fps();
270
+ if (fps !== void 0) context.setFps(fps);
271
+ try {
272
+ return await runFrameRange(context.clock(), rest);
273
+ } finally {
274
+ if (fps !== void 0) context.setFps(previousFps);
275
+ }
276
+ }
277
+
278
+ // src/cli/frames.ts
279
+ function parseFrameSpec(spec) {
280
+ const text = String(spec ?? "").trim();
281
+ const match = /^(-?\d+(?:\.\d+)?)(?:\s*[-:]\s*(-?\d+(?:\.\d+)?))?(?:\s*x\s*(\d+(?:\.\d+)?))?$/i.exec(text);
282
+ if (!match) {
283
+ throw new Error(`--frames expects a range like 1-100, 1-100x2, or a single frame like 42 (got "${spec}")`);
284
+ }
285
+ const start = Number(match[1]);
286
+ const end = match[2] === void 0 ? start : Number(match[2]);
287
+ const step = match[3] === void 0 ? void 0 : Number(match[3]);
288
+ if (end < start) throw new Error(`--frames: end (${end}) must not be before start (${start})`);
289
+ if (step !== void 0 && !(step > 0)) throw new Error(`--frames: step must be greater than 0 (got ${step})`);
290
+ return step === void 0 ? { start, end } : { start, end, step };
291
+ }
292
+ function outputPorts(graph, entryNode) {
293
+ const nodes = entryNode ? [entryNode] : graph.nodes;
294
+ const found = [];
295
+ for (const node of nodes) {
296
+ for (const port of node.outputs) {
297
+ if (port.dataType !== "image") continue;
298
+ if (!entryNode && port.connections.length > 0) continue;
299
+ found.push({ node, port });
300
+ }
301
+ }
302
+ return found;
303
+ }
304
+ function outputSnapshot(graph) {
305
+ return JSON.stringify(graph.nodes.map((node) => [node.id, node.outputs.map((port) => port.value ?? null)]));
306
+ }
307
+ async function cookUntilSettled(graph, entryNode, options = {}) {
308
+ const { fixpoint = false, passes = 8 } = options;
309
+ const dirtyAll = () => {
310
+ for (const node of graph.nodes) node.markDirty?.();
311
+ };
312
+ let previous = fixpoint ? outputSnapshot(graph) : null;
313
+ for (let pass = 0; pass < passes; pass++) {
314
+ await graph.execute(entryNode);
315
+ await graph.scheduler?.whenIdle?.();
316
+ if (graph.nodes.some((node) => node.error !== null)) return;
317
+ const stale = graph.nodes.some((node) => node.cookState === "stale" || node.cookState === "queued");
318
+ if (!fixpoint) {
319
+ if (!stale) return;
320
+ continue;
321
+ }
322
+ const snapshot = outputSnapshot(graph);
323
+ const settled = !stale && snapshot === previous;
324
+ previous = snapshot;
325
+ if (settled) return;
326
+ dirtyAll();
327
+ }
328
+ throw new Error(`graph did not settle after ${passes} cook passes`);
329
+ }
330
+ async function renderFrameRange(options) {
331
+ const { graph, project, out, start, end, step, fps, entryNode, verbose, signal } = options;
332
+ cascade.setGraph(graph);
333
+ const outDirectory = project.resolve(out);
334
+ await fs.mkdir(outDirectory, { recursive: true });
335
+ const files = [];
336
+ const width = sequenceWidth(end);
337
+ const result = await evaluateFrameRange({
338
+ start,
339
+ end,
340
+ step,
341
+ fps,
342
+ signal,
343
+ onFrame: async ({ frame, index, total }) => {
344
+ if (index === 0) for (const node of graph.nodes) node.markDirty();
345
+ await cookUntilSettled(graph, entryNode, { fixpoint: index === 0 });
346
+ const failed = graph.nodes.filter((node) => node.error !== null);
347
+ if (failed.length) {
348
+ const detail = failed.map((node) => `${node.id}: ${node.error?.message ?? "unknown cook error"}`).join("; ");
349
+ throw new Error(`frame ${frame} failed \u2014 ${detail}`);
350
+ }
351
+ const ports = outputPorts(graph, entryNode);
352
+ if (!ports.length) {
353
+ throw new Error(
354
+ "No image output to save: the graph has no unconsumed image port. Name the node to render with --entry-node."
355
+ );
356
+ }
357
+ for (const { node, port } of ports) {
358
+ const source = imagePath(port.value);
359
+ if (!source) continue;
360
+ const name = sequenceFileName(node.id, frame, source, width);
361
+ await fs.copyFile(project.resolveMedia(source), path.join(outDirectory, name));
362
+ files.push(path.posix.join(out, name));
363
+ }
364
+ if (verbose) console.log(` frame ${frame} (${index + 1}/${total})`);
365
+ }
366
+ });
367
+ return { frames: result.frames, files, aborted: result.aborted };
368
+ }
369
+
370
+ export {
371
+ parseFrameSpec,
372
+ outputPorts,
373
+ cookUntilSettled,
374
+ renderFrameRange
375
+ };
376
+ //# sourceMappingURL=chunk-BP7UKF36.js.map
@@ -0,0 +1,201 @@
1
+ import {
2
+ Node,
3
+ packagePathToType,
4
+ registerNodeClasses,
5
+ registerNodeMetadata
6
+ } from "./chunk-CTPLRSWF.js";
7
+ import {
8
+ bundleValueProblem,
9
+ gpuLimitShortfall,
10
+ graphSeed,
11
+ isGpuHost,
12
+ nodeSeed,
13
+ seededRandom,
14
+ selectBundleFields
15
+ } from "./chunk-L6B6FUQP.js";
16
+
17
+ // src/nodes/definition/DefinitionNode.ts
18
+ var silentProgress = { report: () => {
19
+ } };
20
+ function parameterOptions(definition) {
21
+ return {
22
+ ...definition.label ? { label: definition.label } : {},
23
+ ...definition.expression ? { defaultExpression: definition.expression } : {},
24
+ ...definition.action ? { action: definition.action } : {},
25
+ ...definition.min === void 0 ? {} : { min: definition.min },
26
+ ...definition.max === void 0 ? {} : { max: definition.max },
27
+ ...definition.step === void 0 ? {} : { step: definition.step },
28
+ ...definition.options ? {
29
+ // A declared label wins; a bare value is its own label, which is what
30
+ // every definition written before the labelled form meant. Stringifying
31
+ // unconditionally is what turned "Magnific — costs money" into
32
+ // "magnific" and threw away the only notice that it bills per call.
33
+ choices: definition.options.map((option) => option !== null && typeof option === "object" && "value" in option ? {
34
+ value: option.value,
35
+ label: String(option.label ?? option.value),
36
+ ...option.disabled ? { disabled: true } : {}
37
+ } : { value: option, label: String(option) })
38
+ } : {},
39
+ promotable: false
40
+ };
41
+ }
42
+ function bundlePortOptions(port) {
43
+ if (port.type !== "bundle") return {};
44
+ return {
45
+ ...port.schema ? { schema: port.schema } : {},
46
+ ...Array.isArray(port.fields) ? { fieldSelection: port.fields } : port.fields ? { bundleFields: port.fields } : {}
47
+ };
48
+ }
49
+ function attachDefinition(node, registration, capabilities) {
50
+ const definition = registration.definition;
51
+ const pendingTriggers = /* @__PURE__ */ new Map();
52
+ let triggerSequence = 0;
53
+ const container = definition.container;
54
+ if (container === "subnet" || container === "array") {
55
+ node.declaredContainer = container;
56
+ }
57
+ for (const [name, input] of Object.entries(definition.inputs ?? {})) {
58
+ if (input.kind === "trigger") {
59
+ const port = node.in(name, void 0, {
60
+ type: "any",
61
+ ...input.description ? { description: input.description } : {}
62
+ });
63
+ port.portType = "trigger";
64
+ port.onTrigger = async (payload) => {
65
+ const sequence = triggerSequence++;
66
+ pendingTriggers.set(name, {
67
+ id: `${node.id}:${sequence}`,
68
+ runId: `studio:${node.id}:${sequence}`,
69
+ sequence,
70
+ ...payload === void 0 ? {} : { payload },
71
+ source: { kind: "external" }
72
+ });
73
+ node.markDirty();
74
+ await node.execute();
75
+ };
76
+ continue;
77
+ }
78
+ if (input.variadic) {
79
+ node.setVariadic(void 0, { type: input.type });
80
+ continue;
81
+ }
82
+ node.in(name, input.default, {
83
+ type: input.type,
84
+ ...input.min === void 0 ? {} : { min: input.min },
85
+ ...input.max === void 0 ? {} : { max: input.max },
86
+ ...input.step === void 0 ? {} : { step: input.step },
87
+ ...input.options ? {
88
+ choices: input.options.map((value) => ({ value, label: String(value) }))
89
+ } : {},
90
+ ...input.description ? { description: input.description } : {},
91
+ ...bundlePortOptions(input)
92
+ });
93
+ }
94
+ for (const [name, output] of Object.entries(definition.outputs ?? {})) {
95
+ node.out(
96
+ name,
97
+ output.kind === "trigger" ? "trigger" : "param",
98
+ output.kind === "data" ? { type: output.type, description: output.description, ...bundlePortOptions(output) } : { description: output.description }
99
+ );
100
+ }
101
+ for (const [name, prop] of Object.entries(definition.props ?? {})) {
102
+ const parameter = node.param(name, prop.default, {
103
+ type: prop.type,
104
+ ...parameterOptions(prop)
105
+ });
106
+ parameter.documentField = "props";
107
+ }
108
+ const hasTriggerInputs = Object.values(definition.inputs ?? {}).some((input) => input.kind === "trigger");
109
+ let executePromise;
110
+ return async () => {
111
+ if (hasTriggerInputs && pendingTriggers.size === 0) return;
112
+ for (const name of definition.capabilities ?? []) {
113
+ if (!capabilities[name]) {
114
+ throw new Error(`${registration.moduleId} requires the Studio ${name} capability`);
115
+ }
116
+ }
117
+ const execute = await (executePromise ??= registration.loadExecute("browser"));
118
+ const inputValues = {};
119
+ for (const [name, input] of Object.entries(definition.inputs ?? {})) {
120
+ inputValues[name] = input.kind === "trigger" ? pendingTriggers.get(name) : input.variadic ? node.getVariadicInputs().map((port) => port.value ?? void 0) : input.type === "bundle" && input.fields ? selectBundleFields(node.inputs.find((port) => port.name === name)?.value, input.fields) : node.inputs.find((port) => port.name === name)?.value;
121
+ }
122
+ pendingTriggers.clear();
123
+ const outputs = Object.fromEntries(Object.entries(definition.outputs ?? {}).map(([name, output]) => {
124
+ const port = node.outputs.find((candidate) => candidate.name === name);
125
+ if (!port) throw new Error(`Missing output ${registration.moduleId}.${name}`);
126
+ return [name, output.kind === "trigger" ? { trigger: (payload) => port.trigger(payload) } : {
127
+ set: (value) => {
128
+ if (output.type === "bundle") {
129
+ const problem = bundleValueProblem(value, output.fields);
130
+ if (problem) throw new Error(`${node.id}.${name} ${problem}`);
131
+ }
132
+ port.setValue(value);
133
+ }
134
+ }];
135
+ }));
136
+ let nodeCapabilities = capabilities;
137
+ const gpuHost = (definition.capabilities ?? []).includes("gpu") && isGpuHost(capabilities.gpu) ? capabilities.gpu : null;
138
+ if (gpuHost) {
139
+ await gpuHost.ensure();
140
+ const gpu = gpuHost.forNode(node.id);
141
+ const shortfall = gpuLimitShortfall(node.id, definition.gpuLimits, gpu);
142
+ if (shortfall) throw new Error(shortfall);
143
+ nodeCapabilities = { ...capabilities, gpu };
144
+ }
145
+ const seed = nodeSeed(graphSeed({ seed: node.documentSeed }), node.id);
146
+ const context = {
147
+ nodeId: node.id,
148
+ inputs: inputValues,
149
+ outputs,
150
+ props: Object.fromEntries(node.parameters.map((parameter) => [parameter.name, parameter.value])),
151
+ capabilities: nodeCapabilities,
152
+ // The node's own cook signal, not a fresh one that nobody aborts. See
153
+ // `Node.cookSignal` for what that cost.
154
+ signal: node.cookSignal ?? new AbortController().signal,
155
+ progress: silentProgress,
156
+ // The same derivation as the headless runtime, so a seeded node draws
157
+ // the same thing in both hosts.
158
+ seed,
159
+ random: seededRandom(seed)
160
+ };
161
+ await execute(context);
162
+ };
163
+ }
164
+ function configureNode(node, registration, capabilities) {
165
+ node.setFunction(attachDefinition(node, registration, capabilities));
166
+ }
167
+ function registerDefinitionNodes(registrations, capabilities = {}) {
168
+ const libraries = /* @__PURE__ */ new Map();
169
+ for (const registration of registrations) {
170
+ const parts = registration.moduleId.split(".");
171
+ if (parts.length !== 3 || parts[0] !== "cascade") {
172
+ throw new Error(`Studio built-ins require cascade.<library>.<node>: ${registration.moduleId}`);
173
+ }
174
+ const libraryId = parts[1];
175
+ const nodeType = packagePathToType(registration.moduleId);
176
+ const classes = libraries.get(libraryId) ?? {};
177
+ classes[nodeType] = class extends Node {
178
+ constructor(id, graph) {
179
+ super(id, nodeType, graph);
180
+ }
181
+ setup() {
182
+ configureNode(this, registration, capabilities);
183
+ }
184
+ };
185
+ libraries.set(libraryId, classes);
186
+ registerNodeMetadata({
187
+ type: registration.moduleId,
188
+ name: registration.definition.label ?? nodeType,
189
+ icon: registration.definition.icon,
190
+ description: registration.definition.description,
191
+ category: libraryId
192
+ });
193
+ }
194
+ for (const [libraryId, classes] of libraries) registerNodeClasses(libraryId, classes);
195
+ }
196
+
197
+ export {
198
+ attachDefinition,
199
+ registerDefinitionNodes
200
+ };
201
+ //# sourceMappingURL=chunk-BPODRH52.js.map