@field/cascade 0.2.22 → 0.3.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 (228) hide show
  1. package/AGENTS.md +47 -7
  2. package/ARCHITECTURE.md +30 -2
  3. package/CHANGELOG.md +89 -0
  4. package/DESIGN.md +10 -5
  5. package/README.md +103 -7
  6. package/dist/assets/CodeEditor-BADFbTJS.js +623 -0
  7. package/dist/assets/CodePanel-faK6PRfz.js +2 -0
  8. package/dist/assets/CustomNodeDialog-DhlKuviy.js +1 -0
  9. package/dist/assets/{ExportDialog-DWN4nhXI.js → ExportDialog-BJu2Bpvv.js} +2 -2
  10. package/dist/assets/ProjectSettings-D6a6kjqR.js +2 -0
  11. package/dist/assets/SettingsDialog-DJZdIvLC.js +1 -0
  12. package/dist/assets/VersionHistoryDialog-C1X3HOhc.js +3 -0
  13. package/dist/assets/{core-Bji4aZUU.js → core-CiuiuWZi.js} +29 -38
  14. package/dist/assets/{cssMode-B6Z_Q1Kh.js → cssMode-D-NwZSMJ.js} +1 -1
  15. package/dist/assets/{editor.api-jMoW_43X.js → editor.api-DT3WEvBd.js} +1 -1
  16. package/dist/assets/{freemarker2-DvvPV6m5.js → freemarker2-eKNYQaiz.js} +1 -1
  17. package/dist/assets/geo-DUvVbLnx.js +1 -0
  18. package/dist/assets/{handlebars-B8rfuSIa.js → handlebars-C2w1ggK2.js} +1 -1
  19. package/dist/assets/{html-DyHm6JCp.js → html-DQb41jyo.js} +1 -1
  20. package/dist/assets/{htmlMode-E9XLWE5u.js → htmlMode-IhNlh6Lv.js} +1 -1
  21. package/dist/assets/{image-BUcQC7PG.js → image-DMCXBY-_.js} +26 -40
  22. package/dist/assets/index-BDV5xGBZ.css +1 -0
  23. package/dist/assets/index-HTCN44kj.js +212 -0
  24. package/dist/assets/index-client-BoJgsN9M.js +1 -0
  25. package/dist/assets/{javascript-C_bM5PCN.js → javascript-BIT6jMLC.js} +1 -1
  26. package/dist/assets/{jsonMode-BdyVS7G4.js → jsonMode-CNfyB1ro.js} +1 -1
  27. package/dist/assets/{legacy-DIu3XRwl.js → legacy-CAj2_7QE.js} +2 -2
  28. package/dist/assets/{liquid-CDkn3spA.js → liquid-CmSmJmOG.js} +1 -1
  29. package/dist/assets/{lspLanguageFeatures-AB5DMAsr.js → lspLanguageFeatures-BqMm3uMP.js} +1 -1
  30. package/dist/assets/{mdx-BvH_NjDN.js → mdx-6r6YgOFw.js} +1 -1
  31. package/dist/assets/{nodeTypeUtils-CweA3NHd.js → nodeTypeUtils-BBA1JqHx.js} +1 -1
  32. package/dist/assets/{python-B7uii4fi.js → python-x1H0TQuI.js} +1 -1
  33. package/dist/assets/{razor-Z48fWdJc.js → razor-Dzl1vZFm.js} +1 -1
  34. package/dist/assets/{tsMode-BDgacVG4.js → tsMode-n6DMMe3a.js} +1 -1
  35. package/dist/assets/{typescript-Derb6Oqa.js → typescript-ziEz65xN.js} +1 -1
  36. package/dist/assets/{workers-BtoMX_pm.js → workers-D-qUdp-r.js} +1 -1
  37. package/dist/assets/{xml-B1CaFSrG.js → xml-DMZjt5lE.js} +1 -1
  38. package/dist/assets/{yaml-HmIeA0KH.js → yaml-DFe5qZSO.js} +1 -1
  39. package/dist/cli/index.js +16194 -10195
  40. package/dist/cli/runtime/io.ts +93 -7
  41. package/dist/contracts/animation.d.ts +40 -0
  42. package/dist/contracts/animation.js +14 -0
  43. package/dist/contracts/definition.d.ts +39 -1
  44. package/dist/contracts/document.d.ts +15 -0
  45. package/dist/contracts/index.d.ts +1 -0
  46. package/dist/contracts/index.js +1 -0
  47. package/dist/contracts/validate.d.ts +10 -1
  48. package/dist/contracts/validate.js +9 -1
  49. package/dist/graphs/default.cascade +1 -1
  50. package/dist/index.html +5 -5
  51. package/dist/io.d.ts +196 -0
  52. package/dist/io.js +303 -0
  53. package/dist/net.d.ts +1 -0
  54. package/dist/net.js +28 -0
  55. package/dist/runtime/animation/channel.d.ts +108 -0
  56. package/dist/runtime/animation/channel.js +204 -0
  57. package/dist/runtime/animation/frameRange.d.ts +78 -0
  58. package/dist/runtime/animation/frameRange.js +76 -0
  59. package/dist/runtime/animation/index.d.ts +10 -0
  60. package/dist/runtime/animation/index.js +8 -0
  61. package/dist/runtime/builtins/core/random.d.ts +1 -1
  62. package/dist/runtime/builtins/core/remap.d.ts +1 -1
  63. package/dist/runtime/builtins/geo/copyToPoints.d.ts +1 -1
  64. package/dist/runtime/builtins/geo/copyToPoints.js +1 -1
  65. package/dist/runtime/builtins/geo/generators.d.ts +1 -1
  66. package/dist/runtime/builtins/geo/merge.d.ts +1 -1
  67. package/dist/runtime/builtins/geo/svgExport.d.ts +1 -1
  68. package/dist/runtime/builtins/geo/svgExport.js +1 -1
  69. package/dist/runtime/builtins/geo/transform.d.ts +1 -1
  70. package/dist/runtime/builtins/geo/transform.js +1 -1
  71. package/dist/runtime/definition/architecture.d.ts +1 -1
  72. package/dist/runtime/definition/extract.d.ts +1 -1
  73. package/dist/runtime/definition/extract.js +1 -1
  74. package/dist/runtime/error.d.ts +1 -1
  75. package/dist/runtime/expressions/evaluator.d.ts +53 -0
  76. package/dist/runtime/expressions/evaluator.js +193 -0
  77. package/dist/runtime/expressions/index.d.ts +15 -0
  78. package/dist/runtime/expressions/index.js +12 -0
  79. package/dist/runtime/expressions/math.d.ts +75 -0
  80. package/dist/runtime/expressions/math.js +140 -0
  81. package/dist/runtime/expressions/preprocess.d.ts +33 -0
  82. package/dist/runtime/expressions/preprocess.js +102 -0
  83. package/dist/runtime/expressions/scope.d.ts +27 -0
  84. package/dist/runtime/expressions/scope.js +10 -0
  85. package/dist/runtime/expressions/time.d.ts +54 -0
  86. package/dist/runtime/expressions/time.js +71 -0
  87. package/dist/runtime/expressions/types.d.ts +66 -0
  88. package/dist/runtime/expressions/types.js +9 -0
  89. package/dist/runtime/geometry/attributes.d.ts +2 -2
  90. package/dist/runtime/geometry/attributes.js +1 -1
  91. package/dist/runtime/geometry/builder.d.ts +1 -1
  92. package/dist/runtime/geometry/builder.js +1 -1
  93. package/dist/runtime/geometry/copy.d.ts +1 -1
  94. package/dist/runtime/geometry/copy.js +1 -1
  95. package/dist/runtime/geometry/generate.d.ts +1 -1
  96. package/dist/runtime/geometry/groups.d.ts +1 -1
  97. package/dist/runtime/geometry/groups.js +1 -1
  98. package/dist/runtime/geometry/matrix.d.ts +1 -1
  99. package/dist/runtime/geometry/merge.d.ts +1 -1
  100. package/dist/runtime/geometry/merge.js +1 -1
  101. package/dist/runtime/geometry/primitives.d.ts +1 -1
  102. package/dist/runtime/geometry/primitives.js +1 -1
  103. package/dist/runtime/geometry/promote.d.ts +1 -1
  104. package/dist/runtime/geometry/promote.js +1 -1
  105. package/dist/runtime/geometry/resample.d.ts +1 -1
  106. package/dist/runtime/geometry/resample.js +1 -1
  107. package/dist/runtime/geometry/svg.d.ts +1 -1
  108. package/dist/runtime/geometry/svg.js +1 -1
  109. package/dist/runtime/geometry/transform.d.ts +1 -1
  110. package/dist/runtime/geometry/transform.js +1 -1
  111. package/dist/runtime/index.d.ts +4 -1
  112. package/dist/runtime/index.js +4 -1
  113. package/dist/runtime/params/animator.d.ts +74 -0
  114. package/dist/runtime/params/animator.js +137 -0
  115. package/dist/runtime/params/index.d.ts +11 -0
  116. package/dist/runtime/params/index.js +9 -0
  117. package/dist/runtime/params/resolve.d.ts +73 -0
  118. package/dist/runtime/params/resolve.js +62 -0
  119. package/dist/runtime/runtime.d.ts +22 -0
  120. package/dist/runtime/runtime.js +242 -14
  121. package/dist/runtime/types.d.ts +30 -4
  122. package/dist/shell.d.ts +3 -3
  123. package/doc/NODE_REFERENCE.md +395 -0
  124. package/doc/PROJECT_AUTHORING.md +148 -6
  125. package/package.json +26 -3
  126. package/dist/assets/CodeEditor-DoaNEUaM.js +0 -641
  127. package/dist/assets/CodePanel-B0_2XJ-t.js +0 -2
  128. package/dist/assets/CustomNodeDialog-026lzecD.js +0 -1
  129. package/dist/assets/ProjectSettings-BzT164Yx.js +0 -2
  130. package/dist/assets/SettingsDialog-DmHb1V0r.js +0 -1
  131. package/dist/assets/VersionHistoryDialog-hH6VsXLX.js +0 -3
  132. package/dist/assets/geo-CXmCtzMM.js +0 -2
  133. package/dist/assets/index-BXYXSdgh.js +0 -224
  134. package/dist/assets/index-CrNke7SW.css +0 -1
  135. package/dist/assets/index-client-DN_FvjN8.js +0 -1
  136. package/dist/assets/quill-DjZ69wL_.js +0 -121
  137. package/dist/cli/index.js.map +0 -7
  138. package/dist/contracts/capabilities.d.ts.map +0 -1
  139. package/dist/contracts/capabilities.js.map +0 -1
  140. package/dist/contracts/color.d.ts.map +0 -1
  141. package/dist/contracts/color.js.map +0 -1
  142. package/dist/contracts/definition.d.ts.map +0 -1
  143. package/dist/contracts/definition.js.map +0 -1
  144. package/dist/contracts/document.d.ts.map +0 -1
  145. package/dist/contracts/document.js.map +0 -1
  146. package/dist/contracts/geometry.d.ts.map +0 -1
  147. package/dist/contracts/geometry.js.map +0 -1
  148. package/dist/contracts/index.d.ts.map +0 -1
  149. package/dist/contracts/index.js.map +0 -1
  150. package/dist/contracts/schema.d.ts.map +0 -1
  151. package/dist/contracts/schema.js.map +0 -1
  152. package/dist/contracts/validate.d.ts.map +0 -1
  153. package/dist/contracts/validate.js.map +0 -1
  154. package/dist/contracts/values.d.ts.map +0 -1
  155. package/dist/contracts/values.js.map +0 -1
  156. package/dist/graphs/default-orig.cascade +0 -128
  157. package/dist/runtime/browser.d.ts.map +0 -1
  158. package/dist/runtime/browser.js.map +0 -1
  159. package/dist/runtime/builtins/core/index.d.ts.map +0 -1
  160. package/dist/runtime/builtins/core/index.js.map +0 -1
  161. package/dist/runtime/builtins/core/random.d.ts.map +0 -1
  162. package/dist/runtime/builtins/core/random.js.map +0 -1
  163. package/dist/runtime/builtins/core/remap.d.ts.map +0 -1
  164. package/dist/runtime/builtins/core/remap.js.map +0 -1
  165. package/dist/runtime/builtins/core/routing.d.ts.map +0 -1
  166. package/dist/runtime/builtins/core/routing.js.map +0 -1
  167. package/dist/runtime/builtins/core/structural.d.ts.map +0 -1
  168. package/dist/runtime/builtins/core/structural.js.map +0 -1
  169. package/dist/runtime/builtins/geo/copyToPoints.d.ts.map +0 -1
  170. package/dist/runtime/builtins/geo/copyToPoints.js.map +0 -1
  171. package/dist/runtime/builtins/geo/definitions.d.ts.map +0 -1
  172. package/dist/runtime/builtins/geo/definitions.js.map +0 -1
  173. package/dist/runtime/builtins/geo/generators.d.ts.map +0 -1
  174. package/dist/runtime/builtins/geo/generators.js.map +0 -1
  175. package/dist/runtime/builtins/geo/index.d.ts.map +0 -1
  176. package/dist/runtime/builtins/geo/index.js.map +0 -1
  177. package/dist/runtime/builtins/geo/merge.d.ts.map +0 -1
  178. package/dist/runtime/builtins/geo/merge.js.map +0 -1
  179. package/dist/runtime/builtins/geo/namespace.d.ts.map +0 -1
  180. package/dist/runtime/builtins/geo/namespace.js.map +0 -1
  181. package/dist/runtime/builtins/geo/svgExport.d.ts.map +0 -1
  182. package/dist/runtime/builtins/geo/svgExport.js.map +0 -1
  183. package/dist/runtime/builtins/geo/transform.d.ts.map +0 -1
  184. package/dist/runtime/builtins/geo/transform.js.map +0 -1
  185. package/dist/runtime/builtins/index.d.ts.map +0 -1
  186. package/dist/runtime/builtins/index.js.map +0 -1
  187. package/dist/runtime/definition/architecture.d.ts.map +0 -1
  188. package/dist/runtime/definition/architecture.js.map +0 -1
  189. package/dist/runtime/definition/extract.d.ts.map +0 -1
  190. package/dist/runtime/definition/extract.js.map +0 -1
  191. package/dist/runtime/error.d.ts.map +0 -1
  192. package/dist/runtime/error.js.map +0 -1
  193. package/dist/runtime/geometry/attributes.d.ts.map +0 -1
  194. package/dist/runtime/geometry/attributes.js.map +0 -1
  195. package/dist/runtime/geometry/builder.d.ts.map +0 -1
  196. package/dist/runtime/geometry/builder.js.map +0 -1
  197. package/dist/runtime/geometry/copy.d.ts.map +0 -1
  198. package/dist/runtime/geometry/copy.js.map +0 -1
  199. package/dist/runtime/geometry/generate.d.ts.map +0 -1
  200. package/dist/runtime/geometry/generate.js.map +0 -1
  201. package/dist/runtime/geometry/groups.d.ts.map +0 -1
  202. package/dist/runtime/geometry/groups.js.map +0 -1
  203. package/dist/runtime/geometry/index.d.ts.map +0 -1
  204. package/dist/runtime/geometry/index.js.map +0 -1
  205. package/dist/runtime/geometry/matrix.d.ts.map +0 -1
  206. package/dist/runtime/geometry/matrix.js.map +0 -1
  207. package/dist/runtime/geometry/merge.d.ts.map +0 -1
  208. package/dist/runtime/geometry/merge.js.map +0 -1
  209. package/dist/runtime/geometry/primitives.d.ts.map +0 -1
  210. package/dist/runtime/geometry/primitives.js.map +0 -1
  211. package/dist/runtime/geometry/promote.d.ts.map +0 -1
  212. package/dist/runtime/geometry/promote.js.map +0 -1
  213. package/dist/runtime/geometry/resample.d.ts.map +0 -1
  214. package/dist/runtime/geometry/resample.js.map +0 -1
  215. package/dist/runtime/geometry/svg.d.ts.map +0 -1
  216. package/dist/runtime/geometry/svg.js.map +0 -1
  217. package/dist/runtime/geometry/transform.d.ts.map +0 -1
  218. package/dist/runtime/geometry/transform.js.map +0 -1
  219. package/dist/runtime/index.d.ts.map +0 -1
  220. package/dist/runtime/index.js.map +0 -1
  221. package/dist/runtime/node.d.ts.map +0 -1
  222. package/dist/runtime/node.js.map +0 -1
  223. package/dist/runtime/runtime.d.ts.map +0 -1
  224. package/dist/runtime/runtime.js.map +0 -1
  225. package/dist/runtime/types.d.ts.map +0 -1
  226. package/dist/runtime/types.js.map +0 -1
  227. package/dist/shell.js.map +0 -7
  228. package/dist/stage.js.map +0 -7
@@ -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
@@ -0,0 +1,71 @@
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 const DEFAULT_FPS = 30;
11
+ export const MIN_FRAME = 1;
12
+ export class TimeState {
13
+ /** Exact, possibly fractional frame. Never below MIN_FRAME. */
14
+ _frame = MIN_FRAME;
15
+ _fps = DEFAULT_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) {
21
+ if (!Number.isFinite(frame))
22
+ return;
23
+ this._frame = Math.max(MIN_FRAME, frame);
24
+ }
25
+ /** Set the current time in seconds, keeping the sub-frame remainder. */
26
+ setTime(seconds) {
27
+ if (!Number.isFinite(seconds))
28
+ return;
29
+ this.setFrame(seconds * this._fps + MIN_FRAME);
30
+ }
31
+ setFps(fps) {
32
+ if (!Number.isFinite(fps))
33
+ return;
34
+ this._fps = Math.max(1, fps);
35
+ }
36
+ /** Integer frame — $F. */
37
+ get frame() {
38
+ return Math.floor(this._frame);
39
+ }
40
+ /** Fractional frame — $FF. */
41
+ get frameFraction() {
42
+ return this._frame;
43
+ }
44
+ /**
45
+ * Seconds — $T. Derived from the fractional frame, so it round-trips.
46
+ *
47
+ * Zero at the first frame, which is Houdini's convention: $T = ($FF - 1)/$FPS.
48
+ * The previous definition was frame/fps, making $T equal 1/fps at frame 1 —
49
+ * so `sin($T)` started part-way through its cycle and an animation's first
50
+ * frame was never t=0. Changed 2026-09-07 because Marcus named Houdini as the
51
+ * reference for these variables, and because "time starts at zero" is the
52
+ * thing anybody writing an expression will assume.
53
+ */
54
+ get time() {
55
+ return (this._frame - MIN_FRAME) / this._fps;
56
+ }
57
+ /** Frame rate — $FPS. */
58
+ get fps() {
59
+ return this._fps;
60
+ }
61
+ /** Immutable snapshot of the four time variables. */
62
+ snapshot() {
63
+ return {
64
+ frame: this.frame,
65
+ frameFraction: this.frameFraction,
66
+ time: this.time,
67
+ fps: this.fps
68
+ };
69
+ }
70
+ }
71
+ //# sourceMappingURL=time.js.map
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Expression types.
3
+ *
4
+ * These live in the runtime rather than in contracts because a compiled
5
+ * expression carries a function, so it is runtime machinery rather than a
6
+ * serializable DTO. Contracts stays dependency-free and data-only.
7
+ */
8
+ import type { Node as NodeShape } from "./scope.js";
9
+ /**
10
+ * The context an expression is evaluated against. Everything an expression can
11
+ * see is on this object — there is no ambient global, and in particular no
12
+ * clock: the time variables are values placed here by the caller.
13
+ */
14
+ export interface ExpressionContext<TNode = NodeShape> {
15
+ /** seconds — $T */
16
+ time: number;
17
+ /** integer frame — $F */
18
+ frame: number;
19
+ /** fractional frame — $FF */
20
+ fframe: number;
21
+ /** rate — $FPS */
22
+ fps: number;
23
+ /** The node whose parameter is being evaluated. */
24
+ self: TNode;
25
+ ch: (path: string) => number;
26
+ chs: (path: string) => string;
27
+ chv: (path: string) => number[];
28
+ opexist: (path: string) => 0 | 1;
29
+ opinput: (index: number) => TNode | null;
30
+ fit: (value: number, oldMin: number, oldMax: number, newMin: number, newMax: number) => number;
31
+ fit01: (value: number, newMin: number, newMax: number) => number;
32
+ clamp: (value: number, min: number, max: number) => number;
33
+ lerp: (a: number, b: number, t: number) => number;
34
+ smooth: (value: number, min: number, max: number) => number;
35
+ noise: (...coords: number[]) => number;
36
+ random: (seed: number) => number;
37
+ padzero: (digits: number, value: number) => string;
38
+ Math: typeof Math;
39
+ }
40
+ /** A compiled, cached expression ready to evaluate against a context. */
41
+ export interface CompiledExpression<TNode = NodeShape> {
42
+ /** The expression as authored, `$` spellings intact. */
43
+ source: string;
44
+ /** The expression after `$` preprocessing — what was actually compiled. */
45
+ compiledSource: string;
46
+ evaluate: (context: ExpressionContext<TNode>) => unknown;
47
+ /** Parameter paths referenced through ch/chs/chv, for dirty tracking. */
48
+ dependencies: string[];
49
+ /** True if the expression references any time variable, in either spelling. */
50
+ isTimeDependent: boolean;
51
+ error?: string;
52
+ }
53
+ /** Result of evaluating one expression. */
54
+ export interface ExpressionResult {
55
+ value: unknown;
56
+ error?: string;
57
+ }
58
+ /** A referenced path that does not resolve. */
59
+ export interface PathWarning {
60
+ nodeId: string;
61
+ nodePath: string;
62
+ propName: string;
63
+ expression: string;
64
+ brokenPath: string;
65
+ }
66
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Expression types.
3
+ *
4
+ * These live in the runtime rather than in contracts because a compiled
5
+ * expression carries a function, so it is runtime machinery rather than a
6
+ * serializable DTO. Contracts stays dependency-free and data-only.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -1,4 +1,4 @@
1
- import { type AnyAttribute, type Attribute, type AttributeLevel, type AttributeSet, type AttributeStorage, type DetailValue, type ElementLevel, type Geometry, type StringAttribute } from "cascade/contracts";
1
+ import { type AnyAttribute, type Attribute, type AttributeLevel, type AttributeSet, type AttributeStorage, type DetailValue, type ElementLevel, type Geometry, type StringAttribute } from "@field/cascade/contracts";
2
2
  /**
3
3
  * Attribute reads and writes. Every write returns a new `Geometry` and shares
4
4
  * the arrays it did not touch, which is the whole reason the format is
@@ -61,5 +61,5 @@ export interface AttributeCreateOptions {
61
61
  */
62
62
  export declare function attributeCreate(geometry: Geometry, options: AttributeCreateOptions): Geometry;
63
63
  /** A same-storage copy of an attribute's array, for a write-in-place pass. */
64
- export declare function copyAttributeArray(attribute: Attribute): import("cascade/contracts").AttributeArray;
64
+ export declare function copyAttributeArray(attribute: Attribute): import("@field/cascade/contracts").AttributeArray;
65
65
  //# sourceMappingURL=attributes.d.ts.map
@@ -1,4 +1,4 @@
1
- import { attributeArray, attributeArrayFrom, createGeometry, isStringAttribute, levelCount, POSITION_ATTRIBUTE, } from "cascade/contracts";
1
+ import { attributeArray, attributeArrayFrom, createGeometry, isStringAttribute, levelCount, POSITION_ATTRIBUTE, } from "@field/cascade/contracts";
2
2
  /**
3
3
  * Attribute reads and writes. Every write returns a new `Geometry` and shares
4
4
  * the arrays it did not touch, which is the whole reason the format is
@@ -1,4 +1,4 @@
1
- import { emptyGeometry, type AnyAttribute, type AttributeStorage, type DetailValue, type ElementLevel, type Geometry, type PrimitiveKind } from "cascade/contracts";
1
+ import { emptyGeometry, type AnyAttribute, type AttributeStorage, type DetailValue, type ElementLevel, type Geometry, type PrimitiveKind } from "@field/cascade/contracts";
2
2
  export { emptyGeometry };
3
3
  export interface GeometryBuilderOptions {
4
4
  /** 2 by default: the reference workloads are two-component. */
@@ -1,4 +1,4 @@
1
- import { attributeArrayFrom, createGeometry, emptyGeometry, POSITION_ATTRIBUTE, PRIMITIVE_KINDS, } from "cascade/contracts";
1
+ import { attributeArrayFrom, createGeometry, emptyGeometry, POSITION_ATTRIBUTE, PRIMITIVE_KINDS, } from "@field/cascade/contracts";
2
2
  import { createStringAttribute, geometryError } from "./attributes.js";
3
3
  export { emptyGeometry };
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { type Geometry } from "cascade/contracts";
1
+ import { type Geometry } from "@field/cascade/contracts";
2
2
  /**
3
3
  * Copy to Points: one geometry instanced onto every point of another.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { emptyGeometry, isStringAttribute, POSITION_ATTRIBUTE, } from "cascade/contracts";
1
+ import { emptyGeometry, isStringAttribute, POSITION_ATTRIBUTE, } from "@field/cascade/contracts";
2
2
  import { createAttribute, createStringAttribute, geometryError, getPosition, numericAttribute, positionSize, readComponent, readElement, readString, setAttribute, } from "./attributes.js";
3
3
  import { groupIndices, groupMask } from "./groups.js";
4
4
  import { transformMatrix } from "./matrix.js";
@@ -1,4 +1,4 @@
1
- import type { Geometry } from "cascade/contracts";
1
+ import type { Geometry } from "@field/cascade/contracts";
2
2
  /**
3
3
  * The generators. Each returns one primitive in a geometry of its own, so a
4
4
  * graph composes them with `Merge` rather than with a multi-shape node.
@@ -1,4 +1,4 @@
1
- import { type Geometry } from "cascade/contracts";
1
+ import { type Geometry } from "@field/cascade/contracts";
2
2
  /**
3
3
  * Groups are named subsets, stored as one byte per element. A mask is the right
4
4
  * shape because membership is tested far more often than it is enumerated, and
@@ -1,4 +1,4 @@
1
- import { createGeometry, levelCount } from "cascade/contracts";
1
+ import { createGeometry, levelCount } from "@field/cascade/contracts";
2
2
  import { geometryError } from "./attributes.js";
3
3
  const KEYS = {
4
4
  point: "pointGroups",
@@ -1,4 +1,4 @@
1
- import type { Mat3 } from "cascade/contracts";
1
+ import type { Mat3 } from "@field/cascade/contracts";
2
2
  /**
3
3
  * The `mat3` a transform node or an instancing operation needs, built from the
4
4
  * four parameters a Houdini user already knows: translate, rotate, scale and
@@ -1,4 +1,4 @@
1
- import { type AttributeStorage, type Geometry } from "cascade/contracts";
1
+ import { type AttributeStorage, type Geometry } from "@field/cascade/contracts";
2
2
  /**
3
3
  * Merge concatenates geometries and renumbers points, vertices and primitives.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { attributeArray, createGeometry, emptyGeometry, isStringAttribute, levelCount, } from "cascade/contracts";
1
+ import { attributeArray, createGeometry, emptyGeometry, isStringAttribute, levelCount, } from "@field/cascade/contracts";
2
2
  import { createStringAttribute, geometryError, readString } from "./attributes.js";
3
3
  /**
4
4
  * Merge concatenates geometries and renumbers points, vertices and primitives.
@@ -1,4 +1,4 @@
1
- import type { Geometry, PrimitiveKind } from "cascade/contracts";
1
+ import type { Geometry, PrimitiveKind } from "@field/cascade/contracts";
2
2
  /** Topology and measurement reads that the operations share. */
3
3
  export declare function primitiveKind(geometry: Geometry, primitive: number): PrimitiveKind;
4
4
  export declare function isClosed(geometry: Geometry, primitive: number): boolean;
@@ -1,4 +1,4 @@
1
- import { BEZIER_STRIDE, PRIMITIVE_KINDS } from "cascade/contracts";
1
+ import { BEZIER_STRIDE, PRIMITIVE_KINDS } from "@field/cascade/contracts";
2
2
  import { geometryError, positionAttribute, positionSize, } from "./attributes.js";
3
3
  /** Topology and measurement reads that the operations share. */
4
4
  export function primitiveKind(geometry, primitive) {
@@ -1,4 +1,4 @@
1
- import { type AttributeLevel, type ElementLevel, type Geometry } from "cascade/contracts";
1
+ import { type AttributeLevel, type ElementLevel, type Geometry } from "@field/cascade/contracts";
2
2
  /**
3
3
  * Moving an attribute between levels, and copying one between geometries.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { attributeArray, isStringAttribute, levelCount, POSITION_ATTRIBUTE, } from "cascade/contracts";
1
+ import { attributeArray, isStringAttribute, levelCount, POSITION_ATTRIBUTE, } from "@field/cascade/contracts";
2
2
  import { createStringAttribute, geometryError, readString, removeAttribute, removeDetail, setAttribute, setDetail, } from "./attributes.js";
3
3
  /**
4
4
  * Which source elements feed each target element. This one table is the whole
@@ -1,4 +1,4 @@
1
- import { type Geometry } from "cascade/contracts";
1
+ import { type Geometry } from "@field/cascade/contracts";
2
2
  /**
3
3
  * Rewrite each primitive at a uniform arc-length spacing, or at a target
4
4
  * vertex count. Most curve operations want a uniformly sampled chain, so this
@@ -1,4 +1,4 @@
1
- import { attributeArray, createGeometry, isStringAttribute, } from "cascade/contracts";
1
+ import { attributeArray, createGeometry, isStringAttribute, } from "@field/cascade/contracts";
2
2
  import { createStringAttribute, geometryError, readString, } from "./attributes.js";
3
3
  import { groupMask } from "./groups.js";
4
4
  import { arcLengths, isBezier, isClosed, loosePointMask, primitivePoints, } from "./primitives.js";
@@ -1,4 +1,4 @@
1
- import { Color, type Geometry } from "cascade/contracts";
1
+ import { Color, type Geometry } from "@field/cascade/contracts";
2
2
  /**
3
3
  * Geometry to an SVG document. The node that makes the library provably useful,
4
4
  * because it produces a file to print or plot rather than an assertion.