@field/cascade 0.2.22 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/AGENTS.md +47 -7
  2. package/ARCHITECTURE.md +30 -2
  3. package/CHANGELOG.md +107 -0
  4. package/DESIGN.md +10 -5
  5. package/README.md +103 -7
  6. package/dist/assets/CodeEditor-D22fVi-4.js +623 -0
  7. package/dist/assets/CodePanel-DPNEYfcR.js +2 -0
  8. package/dist/assets/CustomNodeDialog-DeHCAvgb.js +1 -0
  9. package/dist/assets/{ExportDialog-DWN4nhXI.js → ExportDialog-BJu2Bpvv.js} +2 -2
  10. package/dist/assets/ProjectSettings-DWpVtWEx.js +2 -0
  11. package/dist/assets/SettingsDialog-za00IS3L.js +1 -0
  12. package/dist/assets/VersionHistoryDialog-DTMGhI0_.js +3 -0
  13. package/dist/assets/{core-Bji4aZUU.js → core-JsyKMO1k.js} +29 -38
  14. package/dist/assets/{cssMode-B6Z_Q1Kh.js → cssMode-emdRheFK.js} +1 -1
  15. package/dist/assets/{editor.api-jMoW_43X.js → editor.api-xHX-fMSo.js} +1 -1
  16. package/dist/assets/{freemarker2-DvvPV6m5.js → freemarker2-C3M8_Gs3.js} +1 -1
  17. package/dist/assets/geo-BAR6lQp5.js +1 -0
  18. package/dist/assets/{handlebars-B8rfuSIa.js → handlebars-CBMurtFk.js} +1 -1
  19. package/dist/assets/{html-DyHm6JCp.js → html-xbLQwknw.js} +1 -1
  20. package/dist/assets/{htmlMode-E9XLWE5u.js → htmlMode-2wKdDTon.js} +1 -1
  21. package/dist/assets/{image-BUcQC7PG.js → image-i1j6nWKe.js} +26 -40
  22. package/dist/assets/index-BDV5xGBZ.css +1 -0
  23. package/dist/assets/index-CMcH8AgP.js +212 -0
  24. package/dist/assets/index-client-BoJgsN9M.js +1 -0
  25. package/dist/assets/{javascript-C_bM5PCN.js → javascript-DJaOo_2I.js} +1 -1
  26. package/dist/assets/{jsonMode-BdyVS7G4.js → jsonMode-Bheas8_0.js} +1 -1
  27. package/dist/assets/{legacy-DIu3XRwl.js → legacy-CAj2_7QE.js} +2 -2
  28. package/dist/assets/{liquid-CDkn3spA.js → liquid-BCj_BGbM.js} +1 -1
  29. package/dist/assets/{lspLanguageFeatures-AB5DMAsr.js → lspLanguageFeatures-BfIsx_xz.js} +1 -1
  30. package/dist/assets/{mdx-BvH_NjDN.js → mdx-DwiQDiVO.js} +1 -1
  31. package/dist/assets/{nodeTypeUtils-CweA3NHd.js → nodeTypeUtils-BBA1JqHx.js} +1 -1
  32. package/dist/assets/{python-B7uii4fi.js → python-BjrB5nqj.js} +1 -1
  33. package/dist/assets/{razor-Z48fWdJc.js → razor-CfoBmGDs.js} +1 -1
  34. package/dist/assets/{tsMode-BDgacVG4.js → tsMode-BcEoMSxp.js} +1 -1
  35. package/dist/assets/{typescript-Derb6Oqa.js → typescript-DIg7TEmQ.js} +1 -1
  36. package/dist/assets/{workers-BtoMX_pm.js → workers-vXYuwtPw.js} +1 -1
  37. package/dist/assets/{xml-B1CaFSrG.js → xml-DexLQIQy.js} +1 -1
  38. package/dist/assets/{yaml-HmIeA0KH.js → yaml-DUnD_1dE.js} +1 -1
  39. package/dist/cli/index.js +16467 -10200
  40. package/dist/cli/runtime/gpu.ts +76 -0
  41. package/dist/cli/runtime/io.ts +93 -7
  42. package/dist/contracts/animation.d.ts +40 -0
  43. package/dist/contracts/animation.js +14 -0
  44. package/dist/contracts/capabilities.d.ts +69 -6
  45. package/dist/contracts/definition.d.ts +60 -2
  46. package/dist/contracts/document.d.ts +15 -0
  47. package/dist/contracts/index.d.ts +1 -0
  48. package/dist/contracts/index.js +1 -0
  49. package/dist/contracts/schema.js +8 -5
  50. package/dist/contracts/validate.d.ts +10 -1
  51. package/dist/contracts/validate.js +20 -3
  52. package/dist/gpu.d.ts +71 -0
  53. package/dist/gpu.js +53 -0
  54. package/dist/graphs/default.cascade +1 -1
  55. package/dist/index.html +5 -5
  56. package/dist/io.d.ts +196 -0
  57. package/dist/io.js +303 -0
  58. package/dist/net.d.ts +1 -0
  59. package/dist/net.js +28 -0
  60. package/dist/runtime/animation/channel.d.ts +108 -0
  61. package/dist/runtime/animation/channel.js +204 -0
  62. package/dist/runtime/animation/frameRange.d.ts +78 -0
  63. package/dist/runtime/animation/frameRange.js +76 -0
  64. package/dist/runtime/animation/index.d.ts +10 -0
  65. package/dist/runtime/animation/index.js +8 -0
  66. package/dist/runtime/builtins/core/random.d.ts +1 -1
  67. package/dist/runtime/builtins/core/remap.d.ts +1 -1
  68. package/dist/runtime/builtins/geo/copyToPoints.d.ts +1 -1
  69. package/dist/runtime/builtins/geo/copyToPoints.js +1 -1
  70. package/dist/runtime/builtins/geo/generators.d.ts +1 -1
  71. package/dist/runtime/builtins/geo/merge.d.ts +1 -1
  72. package/dist/runtime/builtins/geo/svgExport.d.ts +1 -1
  73. package/dist/runtime/builtins/geo/svgExport.js +1 -1
  74. package/dist/runtime/builtins/geo/transform.d.ts +1 -1
  75. package/dist/runtime/builtins/geo/transform.js +1 -1
  76. package/dist/runtime/definition/architecture.d.ts +1 -1
  77. package/dist/runtime/definition/extract.d.ts +1 -1
  78. package/dist/runtime/definition/extract.js +1 -1
  79. package/dist/runtime/error.d.ts +1 -1
  80. package/dist/runtime/expressions/evaluator.d.ts +53 -0
  81. package/dist/runtime/expressions/evaluator.js +193 -0
  82. package/dist/runtime/expressions/index.d.ts +15 -0
  83. package/dist/runtime/expressions/index.js +12 -0
  84. package/dist/runtime/expressions/math.d.ts +75 -0
  85. package/dist/runtime/expressions/math.js +140 -0
  86. package/dist/runtime/expressions/preprocess.d.ts +33 -0
  87. package/dist/runtime/expressions/preprocess.js +102 -0
  88. package/dist/runtime/expressions/scope.d.ts +27 -0
  89. package/dist/runtime/expressions/scope.js +10 -0
  90. package/dist/runtime/expressions/time.d.ts +54 -0
  91. package/dist/runtime/expressions/time.js +71 -0
  92. package/dist/runtime/expressions/types.d.ts +66 -0
  93. package/dist/runtime/expressions/types.js +9 -0
  94. package/dist/runtime/geometry/attributes.d.ts +2 -2
  95. package/dist/runtime/geometry/attributes.js +1 -1
  96. package/dist/runtime/geometry/builder.d.ts +1 -1
  97. package/dist/runtime/geometry/builder.js +1 -1
  98. package/dist/runtime/geometry/copy.d.ts +1 -1
  99. package/dist/runtime/geometry/copy.js +1 -1
  100. package/dist/runtime/geometry/generate.d.ts +1 -1
  101. package/dist/runtime/geometry/groups.d.ts +1 -1
  102. package/dist/runtime/geometry/groups.js +1 -1
  103. package/dist/runtime/geometry/matrix.d.ts +1 -1
  104. package/dist/runtime/geometry/merge.d.ts +1 -1
  105. package/dist/runtime/geometry/merge.js +1 -1
  106. package/dist/runtime/geometry/primitives.d.ts +1 -1
  107. package/dist/runtime/geometry/primitives.js +1 -1
  108. package/dist/runtime/geometry/promote.d.ts +1 -1
  109. package/dist/runtime/geometry/promote.js +1 -1
  110. package/dist/runtime/geometry/resample.d.ts +1 -1
  111. package/dist/runtime/geometry/resample.js +1 -1
  112. package/dist/runtime/geometry/svg.d.ts +1 -1
  113. package/dist/runtime/geometry/svg.js +1 -1
  114. package/dist/runtime/geometry/transform.d.ts +1 -1
  115. package/dist/runtime/geometry/transform.js +1 -1
  116. package/dist/runtime/index.d.ts +4 -1
  117. package/dist/runtime/index.js +4 -1
  118. package/dist/runtime/params/animator.d.ts +74 -0
  119. package/dist/runtime/params/animator.js +137 -0
  120. package/dist/runtime/params/index.d.ts +11 -0
  121. package/dist/runtime/params/index.js +9 -0
  122. package/dist/runtime/params/resolve.d.ts +73 -0
  123. package/dist/runtime/params/resolve.js +62 -0
  124. package/dist/runtime/runtime.d.ts +42 -0
  125. package/dist/runtime/runtime.js +351 -29
  126. package/dist/runtime/types.d.ts +32 -6
  127. package/dist/shell.d.ts +3 -3
  128. package/doc/NODE_REFERENCE.md +395 -0
  129. package/doc/PROJECT_AUTHORING.md +148 -6
  130. package/package.json +30 -3
  131. package/dist/assets/CodeEditor-DoaNEUaM.js +0 -641
  132. package/dist/assets/CodePanel-B0_2XJ-t.js +0 -2
  133. package/dist/assets/CustomNodeDialog-026lzecD.js +0 -1
  134. package/dist/assets/ProjectSettings-BzT164Yx.js +0 -2
  135. package/dist/assets/SettingsDialog-DmHb1V0r.js +0 -1
  136. package/dist/assets/VersionHistoryDialog-hH6VsXLX.js +0 -3
  137. package/dist/assets/geo-CXmCtzMM.js +0 -2
  138. package/dist/assets/index-BXYXSdgh.js +0 -224
  139. package/dist/assets/index-CrNke7SW.css +0 -1
  140. package/dist/assets/index-client-DN_FvjN8.js +0 -1
  141. package/dist/assets/quill-DjZ69wL_.js +0 -121
  142. package/dist/cli/index.js.map +0 -7
  143. package/dist/contracts/capabilities.d.ts.map +0 -1
  144. package/dist/contracts/capabilities.js.map +0 -1
  145. package/dist/contracts/color.d.ts.map +0 -1
  146. package/dist/contracts/color.js.map +0 -1
  147. package/dist/contracts/definition.d.ts.map +0 -1
  148. package/dist/contracts/definition.js.map +0 -1
  149. package/dist/contracts/document.d.ts.map +0 -1
  150. package/dist/contracts/document.js.map +0 -1
  151. package/dist/contracts/geometry.d.ts.map +0 -1
  152. package/dist/contracts/geometry.js.map +0 -1
  153. package/dist/contracts/index.d.ts.map +0 -1
  154. package/dist/contracts/index.js.map +0 -1
  155. package/dist/contracts/schema.d.ts.map +0 -1
  156. package/dist/contracts/schema.js.map +0 -1
  157. package/dist/contracts/validate.d.ts.map +0 -1
  158. package/dist/contracts/validate.js.map +0 -1
  159. package/dist/contracts/values.d.ts.map +0 -1
  160. package/dist/contracts/values.js.map +0 -1
  161. package/dist/graphs/default-orig.cascade +0 -128
  162. package/dist/runtime/browser.d.ts.map +0 -1
  163. package/dist/runtime/browser.js.map +0 -1
  164. package/dist/runtime/builtins/core/index.d.ts.map +0 -1
  165. package/dist/runtime/builtins/core/index.js.map +0 -1
  166. package/dist/runtime/builtins/core/random.d.ts.map +0 -1
  167. package/dist/runtime/builtins/core/random.js.map +0 -1
  168. package/dist/runtime/builtins/core/remap.d.ts.map +0 -1
  169. package/dist/runtime/builtins/core/remap.js.map +0 -1
  170. package/dist/runtime/builtins/core/routing.d.ts.map +0 -1
  171. package/dist/runtime/builtins/core/routing.js.map +0 -1
  172. package/dist/runtime/builtins/core/structural.d.ts.map +0 -1
  173. package/dist/runtime/builtins/core/structural.js.map +0 -1
  174. package/dist/runtime/builtins/geo/copyToPoints.d.ts.map +0 -1
  175. package/dist/runtime/builtins/geo/copyToPoints.js.map +0 -1
  176. package/dist/runtime/builtins/geo/definitions.d.ts.map +0 -1
  177. package/dist/runtime/builtins/geo/definitions.js.map +0 -1
  178. package/dist/runtime/builtins/geo/generators.d.ts.map +0 -1
  179. package/dist/runtime/builtins/geo/generators.js.map +0 -1
  180. package/dist/runtime/builtins/geo/index.d.ts.map +0 -1
  181. package/dist/runtime/builtins/geo/index.js.map +0 -1
  182. package/dist/runtime/builtins/geo/merge.d.ts.map +0 -1
  183. package/dist/runtime/builtins/geo/merge.js.map +0 -1
  184. package/dist/runtime/builtins/geo/namespace.d.ts.map +0 -1
  185. package/dist/runtime/builtins/geo/namespace.js.map +0 -1
  186. package/dist/runtime/builtins/geo/svgExport.d.ts.map +0 -1
  187. package/dist/runtime/builtins/geo/svgExport.js.map +0 -1
  188. package/dist/runtime/builtins/geo/transform.d.ts.map +0 -1
  189. package/dist/runtime/builtins/geo/transform.js.map +0 -1
  190. package/dist/runtime/builtins/index.d.ts.map +0 -1
  191. package/dist/runtime/builtins/index.js.map +0 -1
  192. package/dist/runtime/definition/architecture.d.ts.map +0 -1
  193. package/dist/runtime/definition/architecture.js.map +0 -1
  194. package/dist/runtime/definition/extract.d.ts.map +0 -1
  195. package/dist/runtime/definition/extract.js.map +0 -1
  196. package/dist/runtime/error.d.ts.map +0 -1
  197. package/dist/runtime/error.js.map +0 -1
  198. package/dist/runtime/geometry/attributes.d.ts.map +0 -1
  199. package/dist/runtime/geometry/attributes.js.map +0 -1
  200. package/dist/runtime/geometry/builder.d.ts.map +0 -1
  201. package/dist/runtime/geometry/builder.js.map +0 -1
  202. package/dist/runtime/geometry/copy.d.ts.map +0 -1
  203. package/dist/runtime/geometry/copy.js.map +0 -1
  204. package/dist/runtime/geometry/generate.d.ts.map +0 -1
  205. package/dist/runtime/geometry/generate.js.map +0 -1
  206. package/dist/runtime/geometry/groups.d.ts.map +0 -1
  207. package/dist/runtime/geometry/groups.js.map +0 -1
  208. package/dist/runtime/geometry/index.d.ts.map +0 -1
  209. package/dist/runtime/geometry/index.js.map +0 -1
  210. package/dist/runtime/geometry/matrix.d.ts.map +0 -1
  211. package/dist/runtime/geometry/matrix.js.map +0 -1
  212. package/dist/runtime/geometry/merge.d.ts.map +0 -1
  213. package/dist/runtime/geometry/merge.js.map +0 -1
  214. package/dist/runtime/geometry/primitives.d.ts.map +0 -1
  215. package/dist/runtime/geometry/primitives.js.map +0 -1
  216. package/dist/runtime/geometry/promote.d.ts.map +0 -1
  217. package/dist/runtime/geometry/promote.js.map +0 -1
  218. package/dist/runtime/geometry/resample.d.ts.map +0 -1
  219. package/dist/runtime/geometry/resample.js.map +0 -1
  220. package/dist/runtime/geometry/svg.d.ts.map +0 -1
  221. package/dist/runtime/geometry/svg.js.map +0 -1
  222. package/dist/runtime/geometry/transform.d.ts.map +0 -1
  223. package/dist/runtime/geometry/transform.js.map +0 -1
  224. package/dist/runtime/index.d.ts.map +0 -1
  225. package/dist/runtime/index.js.map +0 -1
  226. package/dist/runtime/node.d.ts.map +0 -1
  227. package/dist/runtime/node.js.map +0 -1
  228. package/dist/runtime/runtime.d.ts.map +0 -1
  229. package/dist/runtime/runtime.js.map +0 -1
  230. package/dist/runtime/types.d.ts.map +0 -1
  231. package/dist/runtime/types.js.map +0 -1
  232. package/dist/shell.js.map +0 -7
  233. package/dist/stage.js.map +0 -7
@@ -1,5 +1,5 @@
1
- import type { AssetCapability, AssetRef, CascadeAbortSignal, CascadeDocument, CascadePreset, Diagnostic, FileCapability, ImageRef, JsonValue, MediaCapability, NodeDefinition, NodeExecute, ProgressReporter, PythonCapability, SerializableValue, ShellCapability, TextureHandle, WebGLCapability } from "cascade/contracts";
2
- export type { CascadeDocument, CascadeDocumentAnnotation, CascadeDocumentConnection, CascadeDocumentNode, CascadePreset, } from "cascade/contracts";
1
+ import type { AssetCapability, AssetRef, CascadeAbortSignal, CascadeDocument, CascadePreset, Diagnostic, FileCapability, ImageRef, JsonValue, MediaCapability, NodeDefinition, NodeExecute, ProgressReporter, PythonCapability, SerializableValue, ShellCapability, TextureHandle, GpuCapability } from "@field/cascade/contracts";
2
+ export type { CascadeDocument, CascadeDocumentAnnotation, CascadeDocumentConnection, CascadeDocumentNode, CascadePreset, } from "@field/cascade/contracts";
3
3
  export interface DefinitionNodeRegistration<D extends NodeDefinition = NodeDefinition> {
4
4
  readonly kind: "definition-v1";
5
5
  readonly moduleId: string;
@@ -31,7 +31,7 @@ export interface RuntimeCapabilities {
31
31
  readonly assets?: AssetCapability;
32
32
  readonly media?: MediaCapability;
33
33
  readonly python?: PythonCapability;
34
- readonly webgl?: WebGLCapability;
34
+ readonly gpu?: GpuCapability;
35
35
  readonly shell?: ShellCapability;
36
36
  readonly serverBridge?: ServerBridgeCapability;
37
37
  }
@@ -88,6 +88,18 @@ export interface RunOptions {
88
88
  readonly target?: RunTarget;
89
89
  readonly signal?: CascadeAbortSignal;
90
90
  readonly runId?: string;
91
+ /**
92
+ * The frame to resolve animated parameters at — a keyframe channel is
93
+ * sampled here and `$F`/`$FF`/`$T` are derived from it. Fractional frames
94
+ * are kept. Omitted leaves the graph on the frame it is already on.
95
+ *
96
+ * A value the caller states rather than a clock the runtime reads: same
97
+ * graph plus same frame gives the same output, which is what makes an
98
+ * offline render reproducible.
99
+ */
100
+ readonly frame?: number;
101
+ /** Frames per second, for `$FPS` and for `$T`. Default 30. */
102
+ readonly fps?: number;
91
103
  }
92
104
  export interface RunResult {
93
105
  readonly runId: string;
@@ -163,13 +175,13 @@ export type RuntimeEvent = {
163
175
  readonly runId: string;
164
176
  readonly nodeId: string;
165
177
  readonly inputName: string;
166
- readonly event: import("cascade/contracts").TriggerEvent;
178
+ readonly event: import("@field/cascade/contracts").TriggerEvent;
167
179
  } | {
168
180
  readonly type: "trigger:delivered";
169
181
  readonly runId: string;
170
182
  readonly nodeId: string;
171
183
  readonly inputName: string;
172
- readonly event: import("cascade/contracts").TriggerEvent;
184
+ readonly event: import("@field/cascade/contracts").TriggerEvent;
173
185
  } | {
174
186
  readonly type: "node:output";
175
187
  readonly runId: string;
@@ -203,6 +215,12 @@ export interface CascadeRuntime {
203
215
  }
204
216
  export interface LoadedCascadeGraph {
205
217
  readonly state: GraphState;
218
+ /**
219
+ * Host-aware static gate: the diagnostics `run` would raise before executing
220
+ * anything, collected instead of thrown. This is what lets a validator see a
221
+ * missing capability, which is a fault it was previously blind to.
222
+ */
223
+ preflight(): readonly Diagnostic[];
206
224
  inspect(): GraphInspection;
207
225
  setInput(nodeId: string, inputName: string, value: unknown): Promise<void>;
208
226
  setGraphInput(name: string, value: unknown): Promise<void>;
@@ -213,6 +231,14 @@ export interface LoadedCascadeGraph {
213
231
  runId?: string;
214
232
  }>): Promise<RunResult>;
215
233
  run(options?: RunOptions): Promise<RunResult>;
234
+ /**
235
+ * Move the graph's clock without cooking. Animated parameters re-resolve
236
+ * immediately, so `inspect()` reports them at this frame.
237
+ */
238
+ setFrame(frame: number): void;
239
+ setFps(fps: number): void;
240
+ getFrame(): number;
241
+ getFps(): number;
216
242
  cancel(reason?: string): void;
217
243
  subscribe(listener: (event: RuntimeEvent) => void): () => void;
218
244
  getOutput(nodeId: string, outputName: string): unknown;
@@ -220,7 +246,7 @@ export interface LoadedCascadeGraph {
220
246
  getOutputs(nodeId: string): ReadonlyMap<string, unknown>;
221
247
  dispose(): Promise<void>;
222
248
  }
223
- export interface NodeRuntimeHostOptions extends Omit<RuntimeCapabilities, "webgl"> {
249
+ export interface NodeRuntimeHostOptions extends Omit<RuntimeCapabilities, "gpu"> {
224
250
  readonly modules: RuntimeHost["modules"];
225
251
  readonly legacyShell?: LegacyShellBindingFactory & Readonly<{
226
252
  kind: "server-direct";
package/dist/shell.d.ts CHANGED
@@ -3,6 +3,6 @@ export {
3
3
  type ShellProcessErrorKind,
4
4
  type ShellRunOptions,
5
5
  type ShellRunResult,
6
- } from 'cascade/contracts';
7
- export declare function run(command: string, args?: readonly string[], options?: import('cascade/contracts').ShellRunOptions): Promise<import('cascade/contracts').ShellRunResult>;
8
- export declare function runJson<T = unknown>(command: string, args?: readonly string[], options?: import('cascade/contracts').ShellRunOptions): Promise<T>;
6
+ } from '@field/cascade/contracts';
7
+ export declare function run(command: string, args?: readonly string[], options?: import('@field/cascade/contracts').ShellRunOptions): Promise<import('@field/cascade/contracts').ShellRunResult>;
8
+ export declare function runJson<T = unknown>(command: string, args?: readonly string[], options?: import('@field/cascade/contracts').ShellRunOptions): Promise<T>;
@@ -0,0 +1,395 @@
1
+ # Node reference
2
+
3
+ **Generated by `scripts/build-node-reference.mjs`. Do not edit — regenerate it.**
4
+
5
+ Read this before searching the codebase for what a node does. It exists because
6
+ that search is expensive and this file is not: the whole catalogue below is under
7
+ 8,000 characters, and working it out by reading source cost one agent about twenty
8
+ tool calls, two of which were the same 1,676-line file.
9
+
10
+ For the `.cascade` document format, expressions, and how to write a node, see
11
+ `AGENTS.md`. **Note in particular that anything time-based is an expression
12
+ rather than a node** — `sin($T) * 40` in a parameter — so there is no oscillator
13
+ node and none is needed.
14
+
15
+ ## Deterministic nodes, with full declarations
16
+
17
+ These are definition-v1: their declarations are literal, so what follows is
18
+ exact rather than a description.
19
+
20
+ ### `cascade.core.Input`
21
+
22
+ Input — Defines a public graph or subnet input
23
+
24
+ Runs on `portable`.
25
+
26
+ **Inputs**
27
+
28
+ | name | type | default | range |
29
+ | --- | --- | --- | --- |
30
+ | `value` | `any` | `null` | |
31
+
32
+ **Props**
33
+
34
+ | name | type | default | range |
35
+ | --- | --- | --- | --- |
36
+ | `inputIndex` | `int` | `0` | |
37
+ | `inputName` | `string` | `""` | |
38
+ | `dataType` | `string` | `"any"` | |
39
+
40
+ **Outputs**
41
+
42
+ | name | type | default | range |
43
+ | --- | --- | --- | --- |
44
+ | `output` | `any` | | |
45
+
46
+ ### `cascade.core.Merge`
47
+
48
+ Merge — Collects inputs into an array
49
+
50
+ Runs on `portable`.
51
+
52
+ **Inputs**
53
+
54
+ | name | type | default | range |
55
+ | --- | --- | --- | --- |
56
+ | `inputs` | `any` | | |
57
+
58
+ **Props**
59
+
60
+ | name | type | default | range |
61
+ | --- | --- | --- | --- |
62
+ | `append` | `bool` | `false` | |
63
+
64
+ **Outputs**
65
+
66
+ | name | type | default | range |
67
+ | --- | --- | --- | --- |
68
+ | `output` | `array` | | |
69
+
70
+ ### `cascade.core.Null`
71
+
72
+ Null — Passes its input through unchanged
73
+
74
+ Runs on `portable`.
75
+
76
+ **Inputs**
77
+
78
+ | name | type | default | range |
79
+ | --- | --- | --- | --- |
80
+ | `input` | `any` | `null` | |
81
+
82
+ **Outputs**
83
+
84
+ | name | type | default | range |
85
+ | --- | --- | --- | --- |
86
+ | `output` | `any` | | |
87
+
88
+ ### `cascade.core.Output`
89
+
90
+ Output — Defines a public graph or subnet output
91
+
92
+ Runs on `portable`.
93
+
94
+ **Inputs**
95
+
96
+ | name | type | default | range |
97
+ | --- | --- | --- | --- |
98
+ | `input` | `any` | `null` | |
99
+
100
+ **Props**
101
+
102
+ | name | type | default | range |
103
+ | --- | --- | --- | --- |
104
+ | `outputIndex` | `int` | `0` | |
105
+ | `outputName` | `string` | `""` | |
106
+ | `dataType` | `string` | `"any"` | |
107
+
108
+ **Outputs**
109
+
110
+ | name | type | default | range |
111
+ | --- | --- | --- | --- |
112
+ | `output` | `any` | | |
113
+
114
+ ### `cascade.core.Random`
115
+
116
+ Random — Generate a deterministic unit value from an explicit seed.
117
+
118
+ Runs on `portable`.
119
+
120
+ **Inputs**
121
+
122
+ | name | type | default | range |
123
+ | --- | --- | --- | --- |
124
+ | `seed` | `int` | `0` | |
125
+ | `sample` | `int` | `0` | |
126
+
127
+ **Outputs**
128
+
129
+ | name | type | default | range |
130
+ | --- | --- | --- | --- |
131
+ | `value` | `float` | | |
132
+
133
+ ### `cascade.core.Remap`
134
+
135
+ Remap — Map a number from one range into another.
136
+
137
+ Runs on `portable`.
138
+
139
+ **Inputs**
140
+
141
+ | name | type | default | range |
142
+ | --- | --- | --- | --- |
143
+ | `value` | `float` | `0` | |
144
+ | `inMin` | `float` | `0` | |
145
+ | `inMax` | `float` | `1` | |
146
+ | `outMin` | `float` | `0` | |
147
+ | `outMax` | `float` | `1` | |
148
+
149
+ **Props**
150
+
151
+ | name | type | default | range |
152
+ | --- | --- | --- | --- |
153
+ | `clamp` | `bool` | `false` | |
154
+
155
+ **Outputs**
156
+
157
+ | name | type | default | range |
158
+ | --- | --- | --- | --- |
159
+ | `result` | `float` | | |
160
+
161
+ ### `cascade.core.Select`
162
+
163
+ Select — Selects an array item by index
164
+
165
+ Runs on `portable`.
166
+
167
+ **Inputs**
168
+
169
+ | name | type | default | range |
170
+ | --- | --- | --- | --- |
171
+ | `array` | `array` | `[]` | |
172
+
173
+ **Props**
174
+
175
+ | name | type | default | range |
176
+ | --- | --- | --- | --- |
177
+ | `index` | `int` | `0` | |
178
+ | `wrap` | `bool` | `true` | |
179
+
180
+ **Outputs**
181
+
182
+ | name | type | default | range |
183
+ | --- | --- | --- | --- |
184
+ | `item` | `any` | | |
185
+ | `count` | `int` | | |
186
+
187
+ ### `cascade.core.Subnet`
188
+
189
+ Subnet — Contains a nested deterministic graph
190
+
191
+ Runs on `portable`.
192
+
193
+ ### `cascade.core.Switch`
194
+
195
+ Switch — Selects one input by index
196
+
197
+ Runs on `portable`.
198
+
199
+ **Inputs**
200
+
201
+ | name | type | default | range |
202
+ | --- | --- | --- | --- |
203
+ | `inputs` | `any` | | |
204
+
205
+ **Props**
206
+
207
+ | name | type | default | range |
208
+ | --- | --- | --- | --- |
209
+ | `index` | `int` | `0` | |
210
+
211
+ **Outputs**
212
+
213
+ | name | type | default | range |
214
+ | --- | --- | --- | --- |
215
+ | `output` | `any` | | |
216
+
217
+ ### `cascade.geo.Circle`
218
+
219
+ Circle — A closed circle, as one cubic Bezier chain or as a polygon of divisions segments.
220
+
221
+ Runs on `portable`.
222
+
223
+ **Inputs**
224
+
225
+ | name | type | default | range |
226
+ | --- | --- | --- | --- |
227
+ | `center` | `vec2` | `[0,0]` | |
228
+ | `radius` | `vec2` | `[1,1]` | |
229
+ | `divisions` | `int` | `32` | |
230
+
231
+ **Props**
232
+
233
+ | name | type | default | range |
234
+ | --- | --- | --- | --- |
235
+ | `type` | `string` | `"bezier"` | |
236
+
237
+ **Outputs**
238
+
239
+ | name | type | default | range |
240
+ | --- | --- | --- | --- |
241
+ | `geometry` | `geometry` | | |
242
+
243
+ ### `cascade.geo.CopyToPoints`
244
+
245
+ Copy to Points — Instance one geometry onto every point of another.
246
+
247
+ Runs on `portable`.
248
+
249
+ **Inputs**
250
+
251
+ | name | type | default | range |
252
+ | --- | --- | --- | --- |
253
+ | `source` | `geometry` | | |
254
+ | `target` | `geometry` | | |
255
+
256
+ **Props**
257
+
258
+ | name | type | default | range |
259
+ | --- | --- | --- | --- |
260
+ | `targetGroup` | `string` | `""` | |
261
+ | `useTargetOrientations` | `bool` | `true` | |
262
+
263
+ **Outputs**
264
+
265
+ | name | type | default | range |
266
+ | --- | --- | --- | --- |
267
+ | `geometry` | `geometry` | | |
268
+
269
+ ### `cascade.geo.Merge`
270
+
271
+ Merge — Concatenate geometries, taking the union of their attributes.
272
+
273
+ Runs on `portable`.
274
+
275
+ **Inputs**
276
+
277
+ | name | type | default | range |
278
+ | --- | --- | --- | --- |
279
+ | `inputs` | `geometry` | | |
280
+
281
+ **Outputs**
282
+
283
+ | name | type | default | range |
284
+ | --- | --- | --- | --- |
285
+ | `geometry` | `geometry` | | |
286
+
287
+ ### `cascade.geo.Rectangle`
288
+
289
+ Rectangle — A closed four-point polygon, counter-clockwise from bottom-left.
290
+
291
+ Runs on `portable`.
292
+
293
+ **Inputs**
294
+
295
+ | name | type | default | range |
296
+ | --- | --- | --- | --- |
297
+ | `size` | `vec2` | `[1,1]` | |
298
+ | `center` | `vec2` | `[0,0]` | |
299
+
300
+ **Outputs**
301
+
302
+ | name | type | default | range |
303
+ | --- | --- | --- | --- |
304
+ | `geometry` | `geometry` | | |
305
+
306
+ ### `cascade.geo.SvgExport`
307
+
308
+ SVG Export — Write geometry as an SVG document, one group element per group.
309
+
310
+ Runs on `portable`.
311
+
312
+ **Inputs**
313
+
314
+ | name | type | default | range |
315
+ | --- | --- | --- | --- |
316
+ | `geometry` | `geometry` | | |
317
+
318
+ **Props**
319
+
320
+ | name | type | default | range |
321
+ | --- | --- | --- | --- |
322
+ | `filename` | `string` | `"geometry.svg"` | |
323
+ | `width` | `float` | `0` | |
324
+ | `height` | `float` | `0` | |
325
+ | `margin` | `float` | `0` | |
326
+ | `stroke` | `color` | `[0,0,0,1]` | |
327
+ | `strokeWidth` | `float` | `1` | |
328
+ | `opacity` | `float` | `1` | 0…1 |
329
+ | `fillMode` | `string` | `"none"` | |
330
+ | `fill` | `color` | `[1,1,1,1]` | |
331
+ | `pointRadius` | `float` | `0.5` | |
332
+ | `precision` | `int` | `3` | 0…15 |
333
+
334
+ **Outputs**
335
+
336
+ | name | type | default | range |
337
+ | --- | --- | --- | --- |
338
+ | `svg` | `string` | | |
339
+ | `asset` | `asset` | | |
340
+
341
+ ### `cascade.geo.Transform`
342
+
343
+ Transform — Translate, rotate and scale geometry about a pivot.
344
+
345
+ Runs on `portable`.
346
+
347
+ **Inputs**
348
+
349
+ | name | type | default | range |
350
+ | --- | --- | --- | --- |
351
+ | `geometry` | `geometry` | | |
352
+ | `translate` | `vec2` | `[0,0]` | |
353
+ | `rotate` | `float` | `0` | |
354
+ | `scale` | `vec2` | `[1,1]` | |
355
+ | `pivot` | `vec2` | `[0,0]` | |
356
+
357
+ **Outputs**
358
+
359
+ | name | type | default | range |
360
+ | --- | --- | --- | --- |
361
+ | `geometry` | `geometry` | | |
362
+
363
+ ## Studio node libraries
364
+
365
+ Class-based nodes, available in Studio. Their parameters are declared in the
366
+ class rather than in a literal, so they are listed here by name only — select
367
+ one in Studio and read the **Definition** panel for its ports, or open the class
368
+ under `src/nodes/<library>/nodes/`.
369
+
370
+ ### Core — `cascade.core.*`
371
+
372
+ **Routing.** `Switch` (Switch between multiple inputs by index), `Merge` (Combine multiple inputs into an array), `Null` (Pass an input through unchanged)
373
+
374
+ **Utility.** `Random` (Generate a deterministic value from a seed), `Remap` (Map a number from one range into another), `Select` (Pick an item from an array by index), `Freeze` (Capture and lock a value)
375
+
376
+ **Network.** `Subnet` (Network container for organizing nodes), `Input` (External input to a subnet), `Output` (Defines subnet output)
377
+
378
+ ### Geometry — `cascade.geo.*`
379
+
380
+ **Geometry.** `cascade.geo.Rectangle` (A closed four-point polygon, counter-clockwise from bottom-left.), `cascade.geo.Circle` (A closed circle, as one cubic Bezier chain or as a polygon of divisions segments.), `cascade.geo.Transform` (Translate, rotate and scale geometry about a pivot.), `cascade.geo.Merge` (Concatenate geometries, taking the union of their attributes.), `cascade.geo.CopyToPoints` (Instance one geometry onto every point of another.), `cascade.geo.SvgExport` (Write geometry as an SVG document, one group element per group.)
381
+
382
+ ### Image — `cascade.image.*`
383
+
384
+ **Create.** `Image` (Load images from files), `Color` (Create a solid color canvas), `Checkers` (Generate a checkerboard pattern), `Noise` (Generate procedural noise patterns), `Ramp` (Generate color ramps), `Text` (Render text with customizable font and style)
385
+
386
+ **Filter.** `Blur` (Gaussian blur), `NormalMap` (Compute normal map from height map)
387
+
388
+ **Transform.** `Transform` (2D rotation, translation, and scaling), `Resize` (Scale or resize an image)
389
+
390
+ **Composite.** `Composite` (Blend two images together)
391
+ ## A project's own nodes
392
+
393
+ Not listed here, because they belong to the project rather than to Cascade. Ask
394
+ the running server: `GET /api/nodes` returns each module with its `runsOn` and
395
+ icon, and `GET /api/nodes/<name>/index.ts` returns its source.
@@ -37,6 +37,7 @@ The portable project shape is:
37
37
  my-artwork/
38
38
  ├── cascade.json
39
39
  ├── index.cascade
40
+ ├── AGENTS.md # written by `cascade new`, for agents working in the project
40
41
  ├── nodes/
41
42
  ├── panels/ # optional Studio-only extensions
42
43
  ├── assets/
@@ -153,6 +154,19 @@ The proxy accepts HTTPS only, refuses undeclared hosts, strips caller-supplied a
153
154
 
154
155
  ## Add a custom node
155
156
 
157
+ Before writing one, check whether you need one. A parameter can hold an expression, so anything time-based is usually a parameter rather than a node. See [Animate a parameter](#animate-a-parameter). A node earns its place when it produces geometry or pixels, or when several parameters share a computation. The built-in library is listed in `NODE_REFERENCE.md`, generated from the definitions themselves; read that before searching for what a node does.
158
+
159
+ There are two styles. Write **definition-v1** for anything new; the dynamic style is what existing project nodes use and it still cooks everywhere.
160
+
161
+ | | Cooked by | Checked by |
162
+ | --- | --- | --- |
163
+ | **definition-v1**: a literal `definition` plus `execute(context)` | the deterministic runtime through `cascade run`, and Studio, which builds the ports from the literal | `cascade check`, which requires this style |
164
+ | **dynamic**: `execute(node, graph)` declaring its own ports and parameters | Studio, and `cascade run` including `--frames` | `cascade validate` and a cook |
165
+
166
+ Studio cooks both, and a Studio graph may mix them. `cascade run` picks one host per document: a graph whose project modules are all definition-v1 runs through the deterministic runtime, and anything else runs through the compatibility engine — which is also why `--frames` is refused on an all-definition-v1 graph, the deterministic runtime owning its own clock. The `AGENTS.md` that `cascade new` writes into a project states which style that project expects, and it is the file to believe over this one for a specific project.
167
+
168
+ ### definition-v1
169
+
156
170
  Create `nodes/Multiply/index.ts`. The directory name defines the module ID `project.Multiply`.
157
171
 
158
172
  ```ts
@@ -177,7 +191,62 @@ export function execute(context: NodeExecutionContext<typeof definition>) {
177
191
  }
178
192
  ```
179
193
 
180
- The definition is the complete static interface. Cascade and code agents can inspect it without importing the module, build the node UI, validate graph connections, and type-check `execute`. Do not create ports, props, or types inside `execute`.
194
+ The definition is the complete static interface. Cascade and code agents can inspect it without importing the module, build the node UI, validate graph connections, and type-check `execute`. In this style `execute` computes and nothing else: it does not create ports, props, or types.
195
+
196
+ Parameters are declared as `props`, beside `inputs` and `outputs`, and each takes a `type` and a `default` plus optional `label`, `description`, `min`, `max`, `step`, `control`, `options`, and `accept`:
197
+
198
+ ```ts
199
+ export const definition = {
200
+ apiVersion: 1,
201
+ label: 'Blend',
202
+ runsOn: 'portable',
203
+ inputs: { value: { kind: 'data', type: 'float', default: 0 } },
204
+ props: { blend: { type: 'float', default: 0.5, min: 0, max: 1, label: 'Blend' } },
205
+ outputs: { result: { kind: 'data', type: 'float' } }
206
+ } as const satisfies NodeDefinition;
207
+ ```
208
+
209
+ #### Prefer the vector types
210
+
211
+ Anything with an x and a y is **one** `vec2`, not two floats. `vec3` and `vec4` likewise, with `vec2i`, `vec3i` and `vec4i` for integer counts and pixel sizes. Positions, offsets, sizes, scales, resolutions and colours with alpha are single ports.
212
+
213
+ ```ts
214
+ // Two props that are really one value
215
+ props: { offset_x: { type: 'float', default: 0 }, offset_y: { type: 'float', default: 0 } }
216
+
217
+ // One prop the type system understands
218
+ props: { offset: { type: 'vec2', default: [0, 0] } }
219
+ ```
220
+
221
+ The reason is not tidiness. Two floats cannot connect to a `vec2` output, take two Inspector rows instead of one control, need two keyframes to animate one movement, and allow a graph to carry an x without its y. The type system knows what a `vec2` is; nothing tells it that `offset_x` and `offset_y` belong together.
222
+
223
+ Keep them separate only when the components differ in kind or in range — a `width` and a `depth` that mean different things are two props, not a `vec2`.
224
+
225
+ ### dynamic
226
+
227
+ A dynamic module declares its interface imperatively, inside `execute`, which runs once to discover the interface and again on every cook. There is no `definition` export; `runsOn` and `icon` are plain exports.
228
+
229
+ ```ts
230
+ import { saveImage, cachePath } from 'cascade/io';
231
+
232
+ export const icon = 'Circle'; // a Lucide icon name
233
+ export const runsOn = 'portable'; // 'portable' | 'browser' | 'server'
234
+
235
+ export async function execute(node: any) {
236
+ const size = node.param('size', 1024, { min: 16, max: 4096, step: 16, type: 'int' }).value;
237
+ const input = node.in('value', 0, { type: 'float' }).value;
238
+ const out = node.out('image', 'param', { type: 'image' });
239
+
240
+ const canvas = new OffscreenCanvas(size, size);
241
+ // ... draw ...
242
+ const path = await saveImage(canvas, cachePath(node.id, '.png'));
243
+ out.setValue({ path, size: [size, size], channels: 'rgb', depth: 'u8', space: 'srgb' });
244
+ }
245
+ ```
246
+
247
+ `param()` is a method on the node the engine hands to `execute`, not an import. It is re-declared on every cook, so it never resets a value, an expression, or a keyframe someone set: reading `.value` gives the *resolved* value for the current frame, whichever of the three bindings is in force. Top-level statements outside `execute` do not run, because the loader imports a real ES module and needs the `execute` export.
248
+
249
+ Prefer `new OffscreenCanvas(width, height)` to `document.createElement('canvas')` when both hosts could run the node, and keep `runsOn: 'browser'` for code that genuinely needs the page. Web technology comes first, meaning Canvas 2D, WebGL and WebGPU. A Python stage is for work that cannot run in a browser at all, such as an exotic ML library. A Python stage costs a process spawn and a round trip per cook, which is invisible on one still frame and fatal to dragging a parameter or playing an animation.
181
250
 
182
251
  Choose the narrowest execution locus:
183
252
 
@@ -231,8 +300,65 @@ Keep implementation nodes behind that boundary so a server or custom frontend
231
300
  does not depend on internal IDs. A child Input/Output node's index defines the
232
301
  matching `input_N`/`output_N` port on its parent Subnet.
233
302
 
234
- Portable nodes must not read ambient time or randomness. Pass frame/time through
235
- root inputs and use seeded Random when a procedural stage needs variation.
303
+ Portable nodes must not read ambient time or randomness. Time reaches a node as
304
+ an expression or a keyframe on a parameter, or through a root input; variation
305
+ comes from seeded `Random`.
306
+
307
+ `NODE_REFERENCE.md` carries the full declarations for these and for the
308
+ `cascade.geo.*` set, generated from the definitions, so the table above is a
309
+ summary rather than the source.
310
+
311
+ ## Animate a parameter
312
+
313
+ A parameter holds up to three things, and they resolve in one order: a **keyframe channel** first, then an **expression**, then the stored **value**. A channel therefore overrides an expression without erasing it: empty the channel and the expression comes back.
314
+
315
+ Expressions use Houdini's variables:
316
+
317
+ | | |
318
+ | --- | --- |
319
+ | `$F` | frame, integer |
320
+ | `$FF` | frame, fractional |
321
+ | `$T` | seconds, zero on the first frame — `($FF - 1) / $FPS` |
322
+ | `$FPS` | rate |
323
+ | `ch("NODE/parm")`, `ch("./parm")` | read another parameter; `chs()` for a string, `chv()` for a vector |
324
+
325
+ The maths library is in scope bare, so `sin(x)` rather than `Math.sin(x)`, with `PI`, `TAU` and `E`. Note that **angles are radians**; `sind`, `cosd`, `tand`, `radians()` and `degrees()` are there for formulae carried over from Houdini. `fit`, `fit01`, `clamp`, `lerp`, `smooth`, `noise`, `random`, `padzero`, `opexist` and `opinput` are available too.
326
+
327
+ So "oscillate the angle" is `sin($T) * 40` in the `angle` parameter. There is no oscillator node and none is needed. Only `ch()`, `chs()` and `chv()` create a dependency, so a parameter that reads another one recooks when it changes.
328
+
329
+ Keyframes are set on a parameter in the Inspector: **alt-click** sets a key at the playhead, **ctrl-click** deletes the key on the current frame, **right-click** opens the Timeline focused on that channel, and the diamond beside the parameter toggles a key for anyone not using a modifier. Interpolation belongs to the key on the left of a segment and is `constant`, `linear`, or `smooth`. Smooth is the default: a Hermite curve with automatic tangents, flat at the first and last key. Outside the keyed range the channel holds its end values.
330
+
331
+ In the document, a channel sits in the node's `props` next to the value and the expression, and `interpolation` is omitted where it is the `smooth` default:
332
+
333
+ ```json
334
+ "props": {
335
+ "angle": {
336
+ "value": 12,
337
+ "expression": "sin($T) * 40",
338
+ "channel": { "keys": [{ "frame": 1, "value": 0, "interpolation": "linear" }, { "frame": 48, "value": 360 }] }
339
+ }
340
+ }
341
+ ```
342
+
343
+ `params` holds plain values; a binding needs the object form under `props`. Playback settings, the frame range and loop, belong to the Timeline panel and are not saved with the document.
344
+
345
+ ## Run a coding agent in the project
346
+
347
+ Studio can run a coding agent in the project directory, editing the same `nodes/` and `.cascade` files the open graph came from. The document is saved before the prompt is sent, and a watcher reloads the graph in place when the agent rewrites it, so nothing needs a page reload. Studio declines that reload while there are unsaved edits rather than choosing a winner.
348
+
349
+ The project decides what may be launched, in `cascade.json`, using the same `commands` allowlist as shell aliases. A scaffolded project ships without one, so nothing is launchable until it is added:
350
+
351
+ ```json
352
+ {
353
+ "name": "My Artwork",
354
+ "commands": { "claude": "claude" },
355
+ "agent": { "claude": { "args": ["--permission-mode", "acceptEdits"] } }
356
+ }
357
+ ```
358
+
359
+ An alias maps to an absolute path, a `~`-relative path, or a bare name on `PATH`; project-relative paths are refused. `agent.<alias>.args` adds fixed flags. `claude` defaults to `--permission-mode acceptEdits`, and an explicit empty array launches it bare. The routes are only available on loopback or in an explicitly enabled trusted-host session.
360
+
361
+ The run belongs to the server rather than the browser: closing the tab leaves the agent working, and reopening the panel replays the buffered transcript and rejoins the stream. Node and parameter names are drag sources carrying their expression address, `TRANSFORM` for a node and `TRANSFORM/tx` for its parameter, so a path can be dragged into the console instead of retyped.
236
362
 
237
363
  ## Integrate external services and AI
238
364
 
@@ -344,6 +470,22 @@ cascade inspect index.cascade
344
470
  cascade run index.cascade
345
471
  ```
346
472
 
473
+ `cascade check` reads definition-v1 modules statically; `cascade validate` checks the document, hierarchy and connections. Neither renders anything, so a green read proves the graph loads and not that it draws. Check a change by running it.
474
+
475
+ Render frames offline with the same command:
476
+
477
+ ```bash
478
+ cascade run index.cascade --frames 1-100
479
+ cascade run index.cascade --frames 1-100x2 --fps 25 --out frames
480
+ cascade run index.cascade --frames 42 --entry-node logo-1024
481
+ ```
482
+
483
+ `--frames` takes a range, a stepped range, or a single frame, and refuses a range it cannot parse rather than repairing it. Files are written as `<out>/<node id>.<frame>.<ext>`, zero-padded to at least four digits, into `renders/` unless `--out` says otherwise. One rule whether the graph has one output or three. Every image output with nothing connected downstream is saved, or the outputs of `--entry-node` when one is named. Cascade stops at the image sequence; a directory of numbered frames is what an encoder takes.
484
+
485
+ Offline, drawing goes through Skia: the optional `@napi-rs/canvas` supplies `OffscreenCanvas`, `Image`, `Path2D` and the rest as globals in the Node process, deliberately not `document`, because that is how a node tells which host it is in. This is why a node that draws on an `OffscreenCanvas` needs no second implementation for headless runs, and why the built-in `cascade.image.*` nodes render under `cascade run` as well as in Studio. A graph that draws nothing runs without the renderer installed.
486
+
487
+ `--frames` renders dynamic graphs. A definition-v1 graph runs through the deterministic runtime, which owns its own clock and has no parameter animation, and the flag is refused rather than silently rendering the first frame repeatedly.
488
+
347
489
  Applications can use `createRuntime()` from `cascade/runtime` directly. Register node modules and host capabilities, load the graph, mutate inputs or presets, run or trigger it, read outputs, then dispose it. This path loads no Svelte or Studio code and works in a backend service or a custom browser application.
348
490
 
349
491
  Server and browser hosts are deliberately explicit. A graph that uses Python or shell stages belongs on the server or behind an application-defined bridge; the runtime does not silently move computation between environments.
@@ -353,8 +495,8 @@ Server and browser hosts are deliberately explicit. A graph that uses Python or
353
495
  Before committing a custom node or graph:
354
496
 
355
497
  1. Type-check the project with its normal TypeScript command.
356
- 2. Run `cascade validate <graph>` and `cascade check <graph>` to check the document plus static definitions, types, hierarchy, and connections without rendering.
357
- 3. Run the graph headlessly when its capabilities are available.
498
+ 2. Run `cascade validate <graph>`, and `cascade check <graph>` for a definition-v1 project, to check the document plus static definitions, types, hierarchy, and connections without rendering.
499
+ 3. Run the graph headlessly when its capabilities are available. This is the step that catches a document that loads, reports its nodes, and draws nothing. The characteristic failure here is silent, so a static pass alone proves little.
358
500
  4. Open Studio only for visual inspection or authoring that benefits from the graph UI.
359
501
 
360
- Treat warnings about legacy dynamic nodes as migration work. New nodes should always use deterministic definitions.
502
+ Dynamic modules are not deprecated and 0.3.0 stopped calling them legacy: the two styles are cooked by different hosts, and a project's own `AGENTS.md` says which one it expects. Convert a node when you need what the other style gives, static inspection and `cascade check` on one side or Studio cooking and `--frames` on the other, rather than on a schedule.