@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
@@ -0,0 +1,193 @@
1
+ /**
2
+ * The neutral expression evaluator.
3
+ *
4
+ * Compiles parameter expressions and evaluates them against a time state and a
5
+ * host-supplied scope. It imports nothing from Studio and touches no platform
6
+ * API, so a headless cook can resolve `sin($T)` on a parameter exactly as the
7
+ * editor does.
8
+ *
9
+ * Determinism: an expression is a parameter resolved *before* execute runs, so
10
+ * the node receives a plain number and stays pure. Same graph plus same frame
11
+ * gives the same output.
12
+ */
13
+ import { clamp, fit, fit01, lerp, MATH_SCOPE, noise, padzero, random, smooth } from "./math.js";
14
+ import { hasTimeReference, preprocessExpression } from "./preprocess.js";
15
+ import { TimeState } from "./time.js";
16
+ const DEPENDENCY_PATTERN = /\b(?:ch|chs|chv)\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
17
+ export class ExpressionEvaluator {
18
+ timeState = new TimeState();
19
+ compiledCache = new Map();
20
+ scope;
21
+ constructor(scope = null) {
22
+ this.scope = scope;
23
+ }
24
+ /** Bind (or rebind) the host scope. Clears nothing: compilation is scope-free. */
25
+ setScope(scope) {
26
+ this.scope = scope;
27
+ }
28
+ getScope() {
29
+ return this.scope;
30
+ }
31
+ // ============ Time ============
32
+ /** Set the current frame. Fractional frames are preserved. */
33
+ setFrame(frame) {
34
+ this.timeState.setFrame(frame);
35
+ }
36
+ /** Set the current time in seconds, keeping the sub-frame remainder. */
37
+ setTime(seconds) {
38
+ this.timeState.setTime(seconds);
39
+ }
40
+ setFps(fps) {
41
+ this.timeState.setFps(fps);
42
+ }
43
+ /** seconds — $T */
44
+ get time() {
45
+ return this.timeState.time;
46
+ }
47
+ /** integer frame — $F */
48
+ get frame() {
49
+ return this.timeState.frame;
50
+ }
51
+ /** fractional frame — $FF */
52
+ get fframe() {
53
+ return this.timeState.frameFraction;
54
+ }
55
+ /** rate — $FPS */
56
+ get fps() {
57
+ return this.timeState.fps;
58
+ }
59
+ // ============ Compile / evaluate ============
60
+ compile(expression) {
61
+ const cached = this.compiledCache.get(expression);
62
+ if (cached)
63
+ return cached;
64
+ const dependencies = this.extractDependencies(expression);
65
+ const isTimeDependent = hasTimeReference(expression);
66
+ const compiledSource = preprocessExpression(expression);
67
+ try {
68
+ const fnBody = `
69
+ with (context) {
70
+ return (${compiledSource});
71
+ }
72
+ `;
73
+ const evaluator = new Function("context", fnBody);
74
+ const compiled = {
75
+ source: expression,
76
+ compiledSource,
77
+ evaluate: evaluator,
78
+ dependencies,
79
+ isTimeDependent
80
+ };
81
+ this.compiledCache.set(expression, compiled);
82
+ return compiled;
83
+ }
84
+ catch (err) {
85
+ return {
86
+ source: expression,
87
+ compiledSource,
88
+ evaluate: () => undefined,
89
+ dependencies,
90
+ isTimeDependent,
91
+ error: err instanceof Error ? err.message : String(err)
92
+ };
93
+ }
94
+ }
95
+ evaluate(expression, node) {
96
+ const compiled = this.compile(expression);
97
+ if (compiled.error)
98
+ return { value: undefined, error: compiled.error };
99
+ try {
100
+ return { value: compiled.evaluate(this.createContext(node)) };
101
+ }
102
+ catch (err) {
103
+ return {
104
+ value: undefined,
105
+ error: err instanceof Error ? err.message : String(err)
106
+ };
107
+ }
108
+ }
109
+ /** Build the context an expression sees. */
110
+ createContext(node) {
111
+ return {
112
+ time: this.timeState.time,
113
+ frame: this.timeState.frame,
114
+ fframe: this.timeState.frameFraction,
115
+ fps: this.timeState.fps,
116
+ self: node,
117
+ ch: (path) => this.ch(node, path),
118
+ chs: (path) => this.chs(node, path),
119
+ chv: (path) => this.chv(node, path),
120
+ opexist: (path) => (this.scope?.nodeExists(node, path) ? 1 : 0),
121
+ opinput: (index) => this.scope?.inputNode(node, index) ?? null,
122
+ fit,
123
+ fit01,
124
+ clamp,
125
+ lerp,
126
+ smooth,
127
+ noise,
128
+ random,
129
+ padzero,
130
+ // Bare maths — `sin($FF * 0.5)` rather than `Math.sin(...)`. Spread
131
+ // before `Math` so nothing here can shadow the namespace itself.
132
+ ...MATH_SCOPE,
133
+ Math
134
+ };
135
+ }
136
+ // ============ Channel functions ============
137
+ ch(node, path) {
138
+ const resolved = this.resolveParam(node, path);
139
+ if (!resolved)
140
+ return 0;
141
+ const value = resolved.value;
142
+ if (typeof value === "number")
143
+ return value;
144
+ if (typeof value === "string")
145
+ return parseFloat(value) || 0;
146
+ if (typeof value === "boolean")
147
+ return value ? 1 : 0;
148
+ return 0;
149
+ }
150
+ chs(node, path) {
151
+ const resolved = this.resolveParam(node, path);
152
+ if (!resolved)
153
+ return "";
154
+ return String(resolved.value ?? "");
155
+ }
156
+ chv(node, path) {
157
+ const resolved = this.resolveParam(node, path);
158
+ if (!resolved)
159
+ return [];
160
+ const value = resolved.value;
161
+ if (Array.isArray(value)) {
162
+ return value.map((v) => (typeof v === "number" ? v : parseFloat(String(v)) || 0));
163
+ }
164
+ if (typeof value === "number")
165
+ return [value];
166
+ return [];
167
+ }
168
+ resolveParam(node, path) {
169
+ return this.scope?.resolveParam(node, path) ?? null;
170
+ }
171
+ /** True if a referenced path resolves from this node. */
172
+ validatePath(fromNode, path) {
173
+ return this.resolveParam(fromNode, path) !== null;
174
+ }
175
+ /** Parameter paths referenced through ch/chs/chv. */
176
+ extractDependencies(expression) {
177
+ const deps = [];
178
+ DEPENDENCY_PATTERN.lastIndex = 0;
179
+ let match;
180
+ while ((match = DEPENDENCY_PATTERN.exec(expression)) !== null) {
181
+ deps.push(match[1]);
182
+ }
183
+ return deps;
184
+ }
185
+ /** True if the expression references time, in either the bare or `$` spelling. */
186
+ hasTimeReference(expression) {
187
+ return hasTimeReference(expression);
188
+ }
189
+ clearCache() {
190
+ this.compiledCache.clear();
191
+ }
192
+ }
193
+ //# sourceMappingURL=evaluator.js.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Parameter expressions, neutral runtime.
3
+ *
4
+ * Compilation, `$` time variables, math utilities and the host scope binding.
5
+ * Depends on nothing outside this folder, so both Studio and a headless host
6
+ * evaluate parameters with the same engine.
7
+ */
8
+ export { ExpressionEvaluator } from "./evaluator.js";
9
+ export { clamp, fit, fit01, lerp, noise, padzero, random, smooth } from "./math.js";
10
+ export { TIME_IDENTIFIERS, TIME_TOKENS, hasTimeReference, preprocessExpression } from "./preprocess.js";
11
+ export { DEFAULT_FPS, MIN_FRAME, TimeState } from "./time.js";
12
+ export type { TimeVariables } from "./time.js";
13
+ export type { ExpressionScope, Node as ExpressionNode } from "./scope.js";
14
+ export type { CompiledExpression, ExpressionContext, ExpressionResult, PathWarning } from "./types.js";
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Parameter expressions, neutral runtime.
3
+ *
4
+ * Compilation, `$` time variables, math utilities and the host scope binding.
5
+ * Depends on nothing outside this folder, so both Studio and a headless host
6
+ * evaluate parameters with the same engine.
7
+ */
8
+ export { ExpressionEvaluator } from "./evaluator.js";
9
+ export { clamp, fit, fit01, lerp, noise, padzero, random, smooth } from "./math.js";
10
+ export { TIME_IDENTIFIERS, TIME_TOKENS, hasTimeReference, preprocessExpression } from "./preprocess.js";
11
+ export { DEFAULT_FPS, MIN_FRAME, TimeState } from "./time.js";
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Expression math utilities.
3
+ *
4
+ * Pure functions, no ambient state, deterministic for a given input — the same
5
+ * contract the rest of the runtime holds. `noise` and `random` are seeded
6
+ * closed forms rather than anything drawn from process state.
7
+ */
8
+ /** Remap a value from one range to another. */
9
+ export declare function fit(value: number, oldMin: number, oldMax: number, newMin: number, newMax: number): number;
10
+ /** Remap a 0..1 value into a new range. */
11
+ export declare function fit01(value: number, newMin: number, newMax: number): number;
12
+ export declare function clamp(value: number, min: number, max: number): number;
13
+ export declare function lerp(a: number, b: number, t: number): number;
14
+ /** Smooth hermite interpolation (smoothstep). */
15
+ export declare function smooth(value: number, min: number, max: number): number;
16
+ /**
17
+ * Perlin-like noise in -1..1. Not true Perlin — a deterministic sum of sines.
18
+ */
19
+ export declare function noise(...coords: number[]): number;
20
+ /** Deterministic random in 0..1 from a seed. */
21
+ export declare function random(seed: number): number;
22
+ /** Zero-pad a number to a given number of digits. */
23
+ export declare function padzero(digits: number, value: number): string;
24
+ /** Degrees to radians. */
25
+ export declare function radians(degrees: number): number;
26
+ /** Radians to degrees. */
27
+ export declare function degrees(radians: number): number;
28
+ /** Sine of an angle in degrees — Houdini's `sin()`. */
29
+ export declare function sind(degrees: number): number;
30
+ /** Cosine of an angle in degrees — Houdini's `cos()`. */
31
+ export declare function cosd(degrees: number): number;
32
+ /** Tangent of an angle in degrees — Houdini's `tan()`. */
33
+ export declare function tand(degrees: number): number;
34
+ /**
35
+ * The bare maths names available to every expression. Spread into the
36
+ * evaluation scope; kept as one object so adding a function is a one-line
37
+ * change here rather than a change in two files.
38
+ */
39
+ export declare const MATH_SCOPE: {
40
+ readonly sin: (x: number) => number;
41
+ readonly cos: (x: number) => number;
42
+ readonly tan: (x: number) => number;
43
+ readonly asin: (x: number) => number;
44
+ readonly acos: (x: number) => number;
45
+ readonly atan: (x: number) => number;
46
+ readonly atan2: (y: number, x: number) => number;
47
+ readonly sinh: (x: number) => number;
48
+ readonly cosh: (x: number) => number;
49
+ readonly tanh: (x: number) => number;
50
+ readonly sind: typeof sind;
51
+ readonly cosd: typeof cosd;
52
+ readonly tand: typeof tand;
53
+ readonly radians: typeof radians;
54
+ readonly degrees: typeof degrees;
55
+ readonly sqrt: (x: number) => number;
56
+ readonly cbrt: (x: number) => number;
57
+ readonly pow: (x: number, y: number) => number;
58
+ readonly exp: (x: number) => number;
59
+ readonly log: (x: number) => number;
60
+ readonly log2: (x: number) => number;
61
+ readonly log10: (x: number) => number;
62
+ readonly hypot: (...values: number[]) => number;
63
+ readonly abs: (x: number) => number;
64
+ readonly sign: (x: number) => number;
65
+ readonly floor: (x: number) => number;
66
+ readonly ceil: (x: number) => number;
67
+ readonly round: (x: number) => number;
68
+ readonly trunc: (x: number) => number;
69
+ readonly min: (...values: number[]) => number;
70
+ readonly max: (...values: number[]) => number;
71
+ readonly PI: number;
72
+ readonly TAU: number;
73
+ readonly E: number;
74
+ };
75
+ //# sourceMappingURL=math.d.ts.map
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Expression math utilities.
3
+ *
4
+ * Pure functions, no ambient state, deterministic for a given input — the same
5
+ * contract the rest of the runtime holds. `noise` and `random` are seeded
6
+ * closed forms rather than anything drawn from process state.
7
+ */
8
+ /** Remap a value from one range to another. */
9
+ export function fit(value, oldMin, oldMax, newMin, newMax) {
10
+ if (oldMax === oldMin)
11
+ return newMin;
12
+ const t = (value - oldMin) / (oldMax - oldMin);
13
+ return newMin + t * (newMax - newMin);
14
+ }
15
+ /** Remap a 0..1 value into a new range. */
16
+ export function fit01(value, newMin, newMax) {
17
+ return newMin + value * (newMax - newMin);
18
+ }
19
+ export function clamp(value, min, max) {
20
+ return Math.max(min, Math.min(max, value));
21
+ }
22
+ export function lerp(a, b, t) {
23
+ return a + (b - a) * t;
24
+ }
25
+ /** Smooth hermite interpolation (smoothstep). */
26
+ export function smooth(value, min, max) {
27
+ if (value <= min)
28
+ return 0;
29
+ if (value >= max)
30
+ return 1;
31
+ const t = (value - min) / (max - min);
32
+ return t * t * (3 - 2 * t);
33
+ }
34
+ /**
35
+ * Perlin-like noise in -1..1. Not true Perlin — a deterministic sum of sines.
36
+ */
37
+ export function noise(...coords) {
38
+ if (coords.length === 0)
39
+ return 0;
40
+ let result = 0;
41
+ const frequencies = [1, 2.3, 4.7, 8.1];
42
+ const amplitudes = [1, 0.5, 0.25, 0.125];
43
+ for (let i = 0; i < frequencies.length; i++) {
44
+ let sum = 0;
45
+ for (let j = 0; j < coords.length; j++) {
46
+ sum += Math.sin(coords[j] * frequencies[i] * (j + 1) * 1.7 + j * 3.14159);
47
+ }
48
+ result += sum * amplitudes[i];
49
+ }
50
+ return Math.sin(result * 0.5);
51
+ }
52
+ /** Deterministic random in 0..1 from a seed. */
53
+ export function random(seed) {
54
+ const x = Math.sin(seed * 12.9898) * 43758.5453;
55
+ return x - Math.floor(x);
56
+ }
57
+ /** Zero-pad a number to a given number of digits. */
58
+ export function padzero(digits, value) {
59
+ return String(Math.floor(value)).padStart(digits, "0");
60
+ }
61
+ // ============ Bare maths, as Houdini exposes it ============
62
+ //
63
+ // An expression language whose users write `sin($T)` and get "sin is not
64
+ // defined" is one they stop trusting. Houdini exposes the whole maths library
65
+ // bare, and so do we.
66
+ //
67
+ // Angles are RADIANS, unlike Houdini's degrees. Two reasons, and the second is
68
+ // the deciding one. `$T` in radians oscillates over 2pi seconds, which is a
69
+ // usable animation period, where in degrees `sin($T)` would take six minutes to
70
+ // come round. And `Math` is also in scope, so a degree-based `sin()` would
71
+ // disagree with `Math.sin()` inside the same expression — a trap far worse than
72
+ // having to type `radians()`. The `sind`/`cosd`/`tand` trio is there for anyone
73
+ // carrying a formula over from Houdini.
74
+ /** Degrees to radians. */
75
+ export function radians(degrees) {
76
+ return (degrees * Math.PI) / 180;
77
+ }
78
+ /** Radians to degrees. */
79
+ export function degrees(radians) {
80
+ return (radians * 180) / Math.PI;
81
+ }
82
+ /** Sine of an angle in degrees — Houdini's `sin()`. */
83
+ export function sind(degrees) {
84
+ return Math.sin(radians(degrees));
85
+ }
86
+ /** Cosine of an angle in degrees — Houdini's `cos()`. */
87
+ export function cosd(degrees) {
88
+ return Math.cos(radians(degrees));
89
+ }
90
+ /** Tangent of an angle in degrees — Houdini's `tan()`. */
91
+ export function tand(degrees) {
92
+ return Math.tan(radians(degrees));
93
+ }
94
+ /**
95
+ * The bare maths names available to every expression. Spread into the
96
+ * evaluation scope; kept as one object so adding a function is a one-line
97
+ * change here rather than a change in two files.
98
+ */
99
+ export const MATH_SCOPE = {
100
+ // Trigonometry, in radians
101
+ sin: Math.sin,
102
+ cos: Math.cos,
103
+ tan: Math.tan,
104
+ asin: Math.asin,
105
+ acos: Math.acos,
106
+ atan: Math.atan,
107
+ atan2: Math.atan2,
108
+ sinh: Math.sinh,
109
+ cosh: Math.cosh,
110
+ tanh: Math.tanh,
111
+ // Trigonometry, in degrees, for formulae carried over from Houdini
112
+ sind,
113
+ cosd,
114
+ tand,
115
+ radians,
116
+ degrees,
117
+ // Powers and logs
118
+ sqrt: Math.sqrt,
119
+ cbrt: Math.cbrt,
120
+ pow: Math.pow,
121
+ exp: Math.exp,
122
+ log: Math.log,
123
+ log2: Math.log2,
124
+ log10: Math.log10,
125
+ hypot: Math.hypot,
126
+ // Rounding and sign
127
+ abs: Math.abs,
128
+ sign: Math.sign,
129
+ floor: Math.floor,
130
+ ceil: Math.ceil,
131
+ round: Math.round,
132
+ trunc: Math.trunc,
133
+ min: Math.min,
134
+ max: Math.max,
135
+ // Constants
136
+ PI: Math.PI,
137
+ TAU: Math.PI * 2,
138
+ E: Math.E
139
+ };
140
+ //# sourceMappingURL=math.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Expression preprocessing: Houdini-style `$` time variables.
3
+ *
4
+ * Expressions compile to raw JS (`return (<expr>)`) evaluated against a context
5
+ * object, so `$F` is not valid input. This pass rewrites the `$` spellings to
6
+ * the plain identifiers the context carries, before compilation. The bare
7
+ * identifiers remain valid aliases — `time`, `frame`, `fframe`, `fps` — so
8
+ * nothing already authored breaks.
9
+ *
10
+ * $F -> frame integer frame
11
+ * $FF -> fframe fractional frame
12
+ * $T -> time seconds
13
+ * $FPS -> fps rate
14
+ *
15
+ * `$` is rewritten only in code position: occurrences inside string or template
16
+ * literals are left alone, so `chs("$F")` stays a string.
17
+ */
18
+ /** The `$` time tokens, longest first so `$FPS` wins over `$F`. */
19
+ export declare const TIME_TOKENS: ReadonlyArray<readonly [token: string, identifier: string]>;
20
+ /** Bare identifiers that carry time, kept as aliases for the `$` spellings. */
21
+ export declare const TIME_IDENTIFIERS: readonly string[];
22
+ /**
23
+ * Rewrite `$` time variables to their plain identifiers.
24
+ * Returns the source unchanged when it contains no `$`.
25
+ */
26
+ export declare function preprocessExpression(source: string): string;
27
+ /**
28
+ * True if an expression references any time variable, in either spelling.
29
+ * This is what sets `isTimeDependent`, which is what lets a per-frame cook
30
+ * recompute only the time-dependent subgraph.
31
+ */
32
+ export declare function hasTimeReference(source: string): boolean;
33
+ //# sourceMappingURL=preprocess.d.ts.map
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Expression preprocessing: Houdini-style `$` time variables.
3
+ *
4
+ * Expressions compile to raw JS (`return (<expr>)`) evaluated against a context
5
+ * object, so `$F` is not valid input. This pass rewrites the `$` spellings to
6
+ * the plain identifiers the context carries, before compilation. The bare
7
+ * identifiers remain valid aliases — `time`, `frame`, `fframe`, `fps` — so
8
+ * nothing already authored breaks.
9
+ *
10
+ * $F -> frame integer frame
11
+ * $FF -> fframe fractional frame
12
+ * $T -> time seconds
13
+ * $FPS -> fps rate
14
+ *
15
+ * `$` is rewritten only in code position: occurrences inside string or template
16
+ * literals are left alone, so `chs("$F")` stays a string.
17
+ */
18
+ /** The `$` time tokens, longest first so `$FPS` wins over `$F`. */
19
+ export const TIME_TOKENS = [
20
+ ["$FPS", "fps"],
21
+ ["$FF", "fframe"],
22
+ ["$F", "frame"],
23
+ ["$T", "time"]
24
+ ];
25
+ /** Bare identifiers that carry time, kept as aliases for the `$` spellings. */
26
+ export const TIME_IDENTIFIERS = ["time", "frame", "fframe", "fps"];
27
+ const DOLLAR_TIME_PATTERN = /\$(FPS|FF|F|T)\b/g;
28
+ const BARE_TIME_PATTERN = /\b(?:time|frame|fframe|fps)\b/;
29
+ const DOLLAR_TO_IDENTIFIER = {
30
+ FPS: "fps",
31
+ FF: "fframe",
32
+ F: "frame",
33
+ T: "time"
34
+ };
35
+ /**
36
+ * Split an expression into code and literal spans, so a rewrite can skip
37
+ * string and template literals. Deliberately small: it handles quotes and
38
+ * escapes, which is all an expression field contains.
39
+ */
40
+ function codeSpans(source) {
41
+ const spans = [];
42
+ let spanStart = 0;
43
+ let index = 0;
44
+ while (index < source.length) {
45
+ const char = source[index];
46
+ if (char === '"' || char === "'" || char === "`") {
47
+ spans.push({ start: spanStart, end: index });
48
+ const quote = char;
49
+ index += 1;
50
+ while (index < source.length) {
51
+ if (source[index] === "\\") {
52
+ index += 2;
53
+ continue;
54
+ }
55
+ if (source[index] === quote) {
56
+ index += 1;
57
+ break;
58
+ }
59
+ index += 1;
60
+ }
61
+ spanStart = index;
62
+ continue;
63
+ }
64
+ index += 1;
65
+ }
66
+ spans.push({ start: spanStart, end: source.length });
67
+ return spans;
68
+ }
69
+ /**
70
+ * Rewrite `$` time variables to their plain identifiers.
71
+ * Returns the source unchanged when it contains no `$`.
72
+ */
73
+ export function preprocessExpression(source) {
74
+ if (!source.includes("$"))
75
+ return source;
76
+ let out = "";
77
+ let cursor = 0;
78
+ for (const span of codeSpans(source)) {
79
+ // Literal text between spans is copied verbatim.
80
+ if (span.start > cursor)
81
+ out += source.slice(cursor, span.start);
82
+ out += source
83
+ .slice(span.start, span.end)
84
+ .replace(DOLLAR_TIME_PATTERN, (_match, key) => DOLLAR_TO_IDENTIFIER[key]);
85
+ cursor = span.end;
86
+ }
87
+ if (cursor < source.length)
88
+ out += source.slice(cursor);
89
+ return out;
90
+ }
91
+ /**
92
+ * True if an expression references any time variable, in either spelling.
93
+ * This is what sets `isTimeDependent`, which is what lets a per-frame cook
94
+ * recompute only the time-dependent subgraph.
95
+ */
96
+ export function hasTimeReference(source) {
97
+ if (BARE_TIME_PATTERN.test(source))
98
+ return true;
99
+ DOLLAR_TIME_PATTERN.lastIndex = 0;
100
+ return DOLLAR_TIME_PATTERN.test(source);
101
+ }
102
+ //# sourceMappingURL=preprocess.js.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The host binding for expression evaluation.
3
+ *
4
+ * The expression engine needs to read parameters off neighbouring nodes and to
5
+ * follow inputs, but it must not know what a node *is* — that is what kept it
6
+ * pinned to Studio's compatibility `Graph`/`Node`. A host supplies a scope; the
7
+ * engine stays neutral and works unchanged in a headless cook.
8
+ */
9
+ /** The minimum a node must offer to be addressable from an expression. */
10
+ export interface Node {
11
+ id: string;
12
+ }
13
+ export interface ExpressionScope<TNode = Node> {
14
+ /**
15
+ * Resolve a channel path relative to a node, e.g. `./radius`,
16
+ * `../blur1/amount`, `/root/node/value`. Return null when it does not
17
+ * resolve — the engine turns that into 0 / '' / [] per accessor.
18
+ */
19
+ resolveParam(from: TNode, path: string): {
20
+ value: unknown;
21
+ } | null;
22
+ /** True if a node exists at the path, relative to `from`. */
23
+ nodeExists(from: TNode, path: string): boolean;
24
+ /** The node connected to `from`'s input at `index`, if any. */
25
+ inputNode(from: TNode, index: number): TNode | null;
26
+ }
27
+ //# sourceMappingURL=scope.d.ts.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The host binding for expression evaluation.
3
+ *
4
+ * The expression engine needs to read parameters off neighbouring nodes and to
5
+ * follow inputs, but it must not know what a node *is* — that is what kept it
6
+ * pinned to Studio's compatibility `Graph`/`Node`. A host supplies a scope; the
7
+ * engine stays neutral and works unchanged in a headless cook.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=scope.js.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Time state for expression evaluation.
3
+ *
4
+ * Frames are stored as a real number so sub-frame time exists. `frame` is the
5
+ * integer frame ($F), `frameFraction` is the exact fractional frame ($FF), and
6
+ * `time` is seconds ($T). Nothing here reads a clock: time is set by the caller
7
+ * (a cook request, a frame-range loop, a transport) and the engine only ever
8
+ * reports what it was told. That is what keeps a cook deterministic.
9
+ */
10
+ export declare const DEFAULT_FPS = 30;
11
+ export declare const MIN_FRAME = 1;
12
+ export declare class TimeState {
13
+ /** Exact, possibly fractional frame. Never below MIN_FRAME. */
14
+ private _frame;
15
+ private _fps;
16
+ /**
17
+ * Set the current frame. Fractional values are preserved; only the lower
18
+ * bound is enforced. Non-finite input is ignored.
19
+ */
20
+ setFrame(frame: number): void;
21
+ /** Set the current time in seconds, keeping the sub-frame remainder. */
22
+ setTime(seconds: number): void;
23
+ setFps(fps: number): void;
24
+ /** Integer frame — $F. */
25
+ get frame(): number;
26
+ /** Fractional frame — $FF. */
27
+ get frameFraction(): number;
28
+ /**
29
+ * Seconds — $T. Derived from the fractional frame, so it round-trips.
30
+ *
31
+ * Zero at the first frame, which is Houdini's convention: $T = ($FF - 1)/$FPS.
32
+ * The previous definition was frame/fps, making $T equal 1/fps at frame 1 —
33
+ * so `sin($T)` started part-way through its cycle and an animation's first
34
+ * frame was never t=0. Changed 2026-09-07 because Marcus named Houdini as the
35
+ * reference for these variables, and because "time starts at zero" is the
36
+ * thing anybody writing an expression will assume.
37
+ */
38
+ get time(): number;
39
+ /** Frame rate — $FPS. */
40
+ get fps(): number;
41
+ /** Immutable snapshot of the four time variables. */
42
+ snapshot(): TimeVariables;
43
+ }
44
+ export interface TimeVariables {
45
+ /** Integer frame — $F */
46
+ frame: number;
47
+ /** Fractional frame — $FF */
48
+ frameFraction: number;
49
+ /** Seconds — $T */
50
+ time: number;
51
+ /** Frame rate — $FPS */
52
+ fps: number;
53
+ }
54
+ //# sourceMappingURL=time.d.ts.map