@field/cascade 0.3.1 → 0.5.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 (129) hide show
  1. package/AGENTS.md +32 -9
  2. package/ARCHITECTURE.md +75 -13
  3. package/CHANGELOG.md +98 -1
  4. package/CONTRIBUTING.md +104 -0
  5. package/DESIGN.md +24 -15
  6. package/README.md +121 -14
  7. package/dist/assets/{CodeEditor-D22fVi-4.js → CodeEditor-dOrU3aQg.js} +7 -7
  8. package/dist/assets/{CodePanel-DPNEYfcR.js → CodePanel-Bqy85qen.js} +2 -2
  9. package/dist/assets/{CustomNodeDialog-DeHCAvgb.js → CustomNodeDialog-DkZlOgjs.js} +1 -1
  10. package/dist/assets/{ProjectSettings-DWpVtWEx.js → ProjectSettings-D192mr6g.js} +2 -2
  11. package/dist/assets/{SettingsDialog-za00IS3L.js → SettingsDialog-BTeeIE3T.js} +1 -1
  12. package/dist/assets/{VersionHistoryDialog-DTMGhI0_.js → VersionHistoryDialog-TZQKkn3Y.js} +1 -1
  13. package/dist/assets/{core-JsyKMO1k.js → core-DwbcGL0o.js} +2 -2
  14. package/dist/assets/{cssMode-emdRheFK.js → cssMode-Dcjn356x.js} +1 -1
  15. package/dist/assets/definitions-C_SY6Bhg.js +1 -0
  16. package/dist/assets/{editor.api-xHX-fMSo.js → editor.api-D7Q83606.js} +1 -1
  17. package/dist/assets/{freemarker2-C3M8_Gs3.js → freemarker2-DCCiHlV6.js} +1 -1
  18. package/dist/assets/geo-CT-wtvaE.js +1 -0
  19. package/dist/assets/{handlebars-CBMurtFk.js → handlebars-BCxC8xu1.js} +1 -1
  20. package/dist/assets/{html-xbLQwknw.js → html-COwepzSd.js} +1 -1
  21. package/dist/assets/{htmlMode-2wKdDTon.js → htmlMode-Dpg7H0rx.js} +1 -1
  22. package/dist/assets/{image-i1j6nWKe.js → image-CMfMMxlC.js} +3 -3
  23. package/dist/assets/index-De1cIZ9N.js +212 -0
  24. package/dist/assets/index-ilgbOu6G.css +1 -0
  25. package/dist/assets/{javascript-DJaOo_2I.js → javascript-CSwWxfLr.js} +1 -1
  26. package/dist/assets/{jsonMode-Bheas8_0.js → jsonMode-BXRriTpj.js} +1 -1
  27. package/dist/assets/{liquid-BCj_BGbM.js → liquid-Cmkfyogc.js} +1 -1
  28. package/dist/assets/{lspLanguageFeatures-BfIsx_xz.js → lspLanguageFeatures-xH9JbZGO.js} +1 -1
  29. package/dist/assets/{mdx-DwiQDiVO.js → mdx-BzrNjNvK.js} +1 -1
  30. package/dist/assets/nodeTypeUtils-BDJsc2Ks.js +1 -0
  31. package/dist/assets/pop-BdKEHslJ.js +1 -0
  32. package/dist/assets/{python-BjrB5nqj.js → python-D4E9jbx7.js} +1 -1
  33. package/dist/assets/{razor-CfoBmGDs.js → razor-B3x9cIEM.js} +1 -1
  34. package/dist/assets/{tsMode-BcEoMSxp.js → tsMode-dhQYPNMZ.js} +1 -1
  35. package/dist/assets/{typescript-DIg7TEmQ.js → typescript-CITkDxAE.js} +1 -1
  36. package/dist/assets/{workers-vXYuwtPw.js → workers-BQvF9bbO.js} +1 -1
  37. package/dist/assets/{xml-DexLQIQy.js → xml-DvHlU3A8.js} +1 -1
  38. package/dist/assets/{yaml-DUnD_1dE.js → yaml-uznsLgs8.js} +1 -1
  39. package/dist/cli/index.js +16465 -12096
  40. package/dist/cli/runtime/gpu.ts +71 -0
  41. package/dist/cli/runtime/io.ts +31 -25
  42. package/dist/contracts/camera.d.ts +70 -0
  43. package/dist/contracts/camera.js +27 -0
  44. package/dist/contracts/capabilities.d.ts +6 -6
  45. package/dist/contracts/definition.d.ts +62 -4
  46. package/dist/contracts/index.d.ts +4 -0
  47. package/dist/contracts/index.js +4 -0
  48. package/dist/contracts/particles.d.ts +54 -0
  49. package/dist/contracts/particles.js +167 -0
  50. package/dist/contracts/scene.d.ts +129 -0
  51. package/dist/contracts/scene.js +73 -0
  52. package/dist/contracts/schema.js +26 -3
  53. package/dist/contracts/series.d.ts +75 -0
  54. package/dist/contracts/series.js +2 -0
  55. package/dist/contracts/validate.js +23 -5
  56. package/dist/contracts/values.d.ts +4 -2
  57. package/dist/contracts/values.js +10 -0
  58. package/dist/gpu.d.ts +17 -0
  59. package/dist/gpu.js +61 -1
  60. package/dist/index.html +3 -3
  61. package/dist/io.d.ts +4 -16
  62. package/dist/io.js +26 -8
  63. package/dist/player/app.js +810 -0
  64. package/dist/player/embed.d.ts +4 -0
  65. package/dist/player/embed.js +77 -0
  66. package/dist/player/types.d.ts +35 -0
  67. package/dist/runtime/builtins/core/camera.d.ts +252 -0
  68. package/dist/runtime/builtins/core/camera.js +149 -0
  69. package/dist/runtime/builtins/core/index.d.ts +4 -2
  70. package/dist/runtime/builtins/core/index.js +8 -2
  71. package/dist/runtime/builtins/core/structural.d.ts +168 -0
  72. package/dist/runtime/builtins/core/structural.js +175 -0
  73. package/dist/runtime/builtins/core/time.d.ts +165 -0
  74. package/dist/runtime/builtins/core/time.js +110 -0
  75. package/dist/runtime/builtins/geo/definitions.d.ts +247 -0
  76. package/dist/runtime/builtins/geo/definitions.js +70 -0
  77. package/dist/runtime/builtins/geo/index.d.ts +2 -1
  78. package/dist/runtime/builtins/geo/index.js +3 -1
  79. package/dist/runtime/builtins/geo/render.d.ts +87 -0
  80. package/dist/runtime/builtins/geo/render.js +87 -0
  81. package/dist/runtime/builtins/geo/svgExport.d.ts +11 -0
  82. package/dist/runtime/builtins/geo/svgExport.js +10 -0
  83. package/dist/runtime/builtins/index.d.ts +5 -2
  84. package/dist/runtime/builtins/index.js +13 -3
  85. package/dist/runtime/builtins/pop/definitions.d.ts +2120 -0
  86. package/dist/runtime/builtins/pop/definitions.js +815 -0
  87. package/dist/runtime/builtins/pop/index.d.ts +2 -0
  88. package/dist/runtime/builtins/pop/index.js +2 -0
  89. package/dist/runtime/builtins/scene/definitions.d.ts +356 -0
  90. package/dist/runtime/builtins/scene/definitions.js +224 -0
  91. package/dist/runtime/builtins/scene/index.d.ts +2 -0
  92. package/dist/runtime/builtins/scene/index.js +2 -0
  93. package/dist/runtime/camera/index.d.ts +60 -0
  94. package/dist/runtime/camera/index.js +234 -0
  95. package/dist/runtime/definition/architecture.js +36 -1
  96. package/dist/runtime/geometry/render.d.ts +109 -0
  97. package/dist/runtime/geometry/render.js +145 -0
  98. package/dist/runtime/geometry/svg.d.ts +29 -0
  99. package/dist/runtime/geometry/svg.js +11 -5
  100. package/dist/runtime/gpu.d.ts +33 -0
  101. package/dist/runtime/gpu.js +218 -0
  102. package/dist/runtime/index.d.ts +4 -0
  103. package/dist/runtime/index.js +4 -0
  104. package/dist/runtime/pop/cache.d.ts +27 -0
  105. package/dist/runtime/pop/cache.js +123 -0
  106. package/dist/runtime/pop/forces.d.ts +202 -0
  107. package/dist/runtime/pop/forces.js +519 -0
  108. package/dist/runtime/pop/index.d.ts +5 -0
  109. package/dist/runtime/pop/index.js +5 -0
  110. package/dist/runtime/pop/state.d.ts +136 -0
  111. package/dist/runtime/pop/state.js +283 -0
  112. package/dist/runtime/pop/trail.d.ts +62 -0
  113. package/dist/runtime/pop/trail.js +142 -0
  114. package/dist/runtime/runtime.js +284 -9
  115. package/dist/runtime/scene/index.d.ts +50 -0
  116. package/dist/runtime/scene/index.js +94 -0
  117. package/dist/runtime/series/index.d.ts +41 -0
  118. package/dist/runtime/series/index.js +167 -0
  119. package/dist/runtime/types.d.ts +1 -1
  120. package/dist/studio/panel.d.ts +68 -1
  121. package/doc/HEADLESS_GPU.md +160 -0
  122. package/doc/NODE_REFERENCE.md +408 -1
  123. package/doc/PROJECT_AUTHORING.md +118 -20
  124. package/doc/WEB_PLAYER.md +131 -0
  125. package/package.json +25 -3
  126. package/dist/assets/geo-BAR6lQp5.js +0 -1
  127. package/dist/assets/index-BDV5xGBZ.css +0 -1
  128. package/dist/assets/index-CMcH8AgP.js +0 -212
  129. package/dist/assets/nodeTypeUtils-BBA1JqHx.js +0 -1
@@ -0,0 +1,2 @@
1
+ export * from "./definitions.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,2 @@
1
+ export * from "./definitions.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,356 @@
1
+ import { type NodeExecutionContext, type Scene } from "@field/cascade/contracts";
2
+ import type { DefinitionNodeRegistration } from "../../types.js";
3
+ /**
4
+ * The one place the scene built-in namespace is written down.
5
+ *
6
+ * `cascade.scene.*` rather than `cascade.geo.*`, and that is a real distinction
7
+ * rather than tidiness: `cascade.geo` is a *medium* — points, primitives,
8
+ * attributes, operations that take geometry and return geometry. A scene is not
9
+ * geometry. It is an assembly of geometry with a viewpoint and lights, and it is
10
+ * the thing you look at rather than the thing you make.
11
+ *
12
+ * The counter-argument, and it is the one this codebase has already accepted
13
+ * once: a namespace with two nodes in it is a promise rather than a structure.
14
+ * `PLAN viewport` says exactly that about `cascade.render.*`, and prefers
15
+ * `cascade.geo.Render` for the render node on those grounds. The difference is
16
+ * that this namespace has a **type** behind it — `scene`, in the core type list
17
+ * — and a type is the thing a namespace is allowed to be named after.
18
+ */
19
+ export declare const SCENE_NAMESPACE = "cascade.scene";
20
+ export declare function sceneModuleId(name: string): string;
21
+ /**
22
+ * `cascade.scene.Scene` — geometry, a camera and lights, assembled.
23
+ *
24
+ * Marcus's design, 2026-09-09: *"a 'scene' root node with a variadic input …
25
+ * a 'scene' node with a camera, lights and geometry attached becomes a '3d
26
+ * scene'."*
27
+ *
28
+ * ## Why one variadic `any` input rather than three typed ones
29
+ *
30
+ * Three inputs — a variadic `geometry`, a single `camera`, a variadic `light` —
31
+ * would be better typed, and the connection rules would then refuse a wrong
32
+ * wire before the node ever ran. That is a real loss and it is taken
33
+ * deliberately, because the gesture Marcus described is *attach things to it*,
34
+ * and a single port is the difference between dropping a camera on the node and
35
+ * hunting for which of three stacked ports accepts it. Houdini's own `/obj`
36
+ * network works the same way: everything in it is in the scene by being there,
37
+ * not by being wired to the right socket.
38
+ *
39
+ * What that costs is paid back at execute time rather than ignored. Every input
40
+ * is classified, and **anything unclassifiable throws** naming the port and the
41
+ * value's shape — the alternative is a scene that silently omits what you
42
+ * attached, which is the failure mode this codebase keeps writing rules about.
43
+ * Two cameras throw for the same reason: picking one is a guess, and a guess
44
+ * about which way the camera is pointing is not recoverable by looking at the
45
+ * result.
46
+ *
47
+ * A nested scene is flattened. That makes `Scene` associative — assembling two
48
+ * sub-scenes gives the same result as assembling their contents — and it is
49
+ * what lets a big graph build a scene in parts.
50
+ */
51
+ export declare const sceneDefinition: {
52
+ readonly apiVersion: 1;
53
+ readonly label: "Scene";
54
+ readonly description: "Geometry, a camera and lights, assembled into a scene. Attach anything to the input.";
55
+ readonly icon: "Box";
56
+ readonly runsOn: "portable";
57
+ readonly inputs: {
58
+ readonly inputs: {
59
+ readonly kind: "data";
60
+ readonly type: "any";
61
+ readonly variadic: true;
62
+ readonly description: "Geometry, one camera, and lights, in any order. Scenes are flattened.";
63
+ };
64
+ };
65
+ readonly outputs: {
66
+ readonly scene: {
67
+ readonly kind: "data";
68
+ readonly type: "scene";
69
+ };
70
+ };
71
+ readonly props: {};
72
+ };
73
+ export declare function executeScene(context: NodeExecutionContext<typeof sceneDefinition>): void;
74
+ /**
75
+ * `cascade.scene.Light` — Houdini's `/obj/hlight`, as a node.
76
+ *
77
+ * **Nothing shades with these yet**, and that is stated in the description so
78
+ * that a light which appears to do nothing is understood rather than reported
79
+ * as broken. Every piece in the vault today is lines — `cloud-volumes` is a
80
+ * volume, `particle-type` is trails, `cloud-plots` is marks — so there is no
81
+ * surface to light, and inventing a shading model before something needs one is
82
+ * how you get a shading model nobody wants.
83
+ *
84
+ * It exists now because `Scene` accepts lights now. A type that gains a field
85
+ * later invalidates every serialized scene and every node that builds one; the
86
+ * node is the cheap half of a decision already taken in the type.
87
+ */
88
+ export declare const lightDefinition: {
89
+ readonly apiVersion: 1;
90
+ readonly label: "Light";
91
+ readonly description: "A light, following Houdini's /obj/hlight. Nothing shades with lights yet — the viewport draws wireframe and points.";
92
+ readonly icon: "Lightbulb";
93
+ readonly runsOn: "portable";
94
+ readonly inputs: {
95
+ readonly translate: {
96
+ readonly kind: "data";
97
+ readonly type: "vec3";
98
+ readonly default: readonly [0, 0, 5];
99
+ readonly description: "World position. Ignored for a distant or ambient light.";
100
+ };
101
+ readonly direction: {
102
+ readonly kind: "data";
103
+ readonly type: "vec3";
104
+ readonly default: readonly [0, 0, -1];
105
+ readonly description: "Direction the light points. Used by distant and spot.";
106
+ };
107
+ readonly color: {
108
+ readonly kind: "data";
109
+ readonly type: "color";
110
+ readonly default: readonly [1, 1, 1, 1];
111
+ readonly description: "Houdini light_color.";
112
+ };
113
+ readonly intensity: {
114
+ readonly kind: "data";
115
+ readonly type: "float";
116
+ readonly default: 1;
117
+ readonly min: 0;
118
+ readonly description: "Houdini light_intensity.";
119
+ };
120
+ };
121
+ readonly outputs: {
122
+ readonly light: {
123
+ readonly kind: "data";
124
+ readonly type: "light";
125
+ };
126
+ };
127
+ readonly props: {
128
+ readonly type: {
129
+ readonly type: "string";
130
+ readonly default: "point";
131
+ readonly label: "Light Type";
132
+ readonly control: "select";
133
+ readonly options: readonly [{
134
+ readonly value: "point";
135
+ readonly label: "Point";
136
+ }, {
137
+ readonly value: "distant";
138
+ readonly label: "Distant - directional";
139
+ }, {
140
+ readonly value: "ambient";
141
+ readonly label: "Ambient";
142
+ }, {
143
+ readonly value: "spot";
144
+ readonly label: "Spot";
145
+ }];
146
+ readonly description: "Houdini light_type. Distant is what other renderers call directional.";
147
+ };
148
+ readonly coneangle: {
149
+ readonly type: "float";
150
+ readonly default: 45;
151
+ readonly min: 0;
152
+ readonly max: 180;
153
+ readonly label: "Cone Angle";
154
+ readonly description: "Degrees. Spot only.";
155
+ };
156
+ };
157
+ };
158
+ export declare function executeLight(context: NodeExecutionContext<typeof lightDefinition>): void;
159
+ export declare const sceneRegistration: {
160
+ kind: "definition-v1";
161
+ moduleId: string;
162
+ definition: {
163
+ readonly apiVersion: 1;
164
+ readonly label: "Scene";
165
+ readonly description: "Geometry, a camera and lights, assembled into a scene. Attach anything to the input.";
166
+ readonly icon: "Box";
167
+ readonly runsOn: "portable";
168
+ readonly inputs: {
169
+ readonly inputs: {
170
+ readonly kind: "data";
171
+ readonly type: "any";
172
+ readonly variadic: true;
173
+ readonly description: "Geometry, one camera, and lights, in any order. Scenes are flattened.";
174
+ };
175
+ };
176
+ readonly outputs: {
177
+ readonly scene: {
178
+ readonly kind: "data";
179
+ readonly type: "scene";
180
+ };
181
+ };
182
+ readonly props: {};
183
+ };
184
+ loadExecute: () => Promise<typeof executeScene>;
185
+ };
186
+ export declare const lightRegistration: {
187
+ kind: "definition-v1";
188
+ moduleId: string;
189
+ definition: {
190
+ readonly apiVersion: 1;
191
+ readonly label: "Light";
192
+ readonly description: "A light, following Houdini's /obj/hlight. Nothing shades with lights yet — the viewport draws wireframe and points.";
193
+ readonly icon: "Lightbulb";
194
+ readonly runsOn: "portable";
195
+ readonly inputs: {
196
+ readonly translate: {
197
+ readonly kind: "data";
198
+ readonly type: "vec3";
199
+ readonly default: readonly [0, 0, 5];
200
+ readonly description: "World position. Ignored for a distant or ambient light.";
201
+ };
202
+ readonly direction: {
203
+ readonly kind: "data";
204
+ readonly type: "vec3";
205
+ readonly default: readonly [0, 0, -1];
206
+ readonly description: "Direction the light points. Used by distant and spot.";
207
+ };
208
+ readonly color: {
209
+ readonly kind: "data";
210
+ readonly type: "color";
211
+ readonly default: readonly [1, 1, 1, 1];
212
+ readonly description: "Houdini light_color.";
213
+ };
214
+ readonly intensity: {
215
+ readonly kind: "data";
216
+ readonly type: "float";
217
+ readonly default: 1;
218
+ readonly min: 0;
219
+ readonly description: "Houdini light_intensity.";
220
+ };
221
+ };
222
+ readonly outputs: {
223
+ readonly light: {
224
+ readonly kind: "data";
225
+ readonly type: "light";
226
+ };
227
+ };
228
+ readonly props: {
229
+ readonly type: {
230
+ readonly type: "string";
231
+ readonly default: "point";
232
+ readonly label: "Light Type";
233
+ readonly control: "select";
234
+ readonly options: readonly [{
235
+ readonly value: "point";
236
+ readonly label: "Point";
237
+ }, {
238
+ readonly value: "distant";
239
+ readonly label: "Distant - directional";
240
+ }, {
241
+ readonly value: "ambient";
242
+ readonly label: "Ambient";
243
+ }, {
244
+ readonly value: "spot";
245
+ readonly label: "Spot";
246
+ }];
247
+ readonly description: "Houdini light_type. Distant is what other renderers call directional.";
248
+ };
249
+ readonly coneangle: {
250
+ readonly type: "float";
251
+ readonly default: 45;
252
+ readonly min: 0;
253
+ readonly max: 180;
254
+ readonly label: "Cone Angle";
255
+ readonly description: "Degrees. Spot only.";
256
+ };
257
+ };
258
+ };
259
+ loadExecute: () => Promise<typeof executeLight>;
260
+ };
261
+ export declare const sceneNodeRegistrations: readonly DefinitionNodeRegistration[];
262
+ /** `[moduleId, definition]` pairs, for a host building a node palette. The same
263
+ * shape `geoNodeDefinitions` and `popNodeDefinitions` have. */
264
+ export declare const sceneNodeDefinitions: readonly [readonly [string, {
265
+ readonly apiVersion: 1;
266
+ readonly label: "Scene";
267
+ readonly description: "Geometry, a camera and lights, assembled into a scene. Attach anything to the input.";
268
+ readonly icon: "Box";
269
+ readonly runsOn: "portable";
270
+ readonly inputs: {
271
+ readonly inputs: {
272
+ readonly kind: "data";
273
+ readonly type: "any";
274
+ readonly variadic: true;
275
+ readonly description: "Geometry, one camera, and lights, in any order. Scenes are flattened.";
276
+ };
277
+ };
278
+ readonly outputs: {
279
+ readonly scene: {
280
+ readonly kind: "data";
281
+ readonly type: "scene";
282
+ };
283
+ };
284
+ readonly props: {};
285
+ }], readonly [string, {
286
+ readonly apiVersion: 1;
287
+ readonly label: "Light";
288
+ readonly description: "A light, following Houdini's /obj/hlight. Nothing shades with lights yet — the viewport draws wireframe and points.";
289
+ readonly icon: "Lightbulb";
290
+ readonly runsOn: "portable";
291
+ readonly inputs: {
292
+ readonly translate: {
293
+ readonly kind: "data";
294
+ readonly type: "vec3";
295
+ readonly default: readonly [0, 0, 5];
296
+ readonly description: "World position. Ignored for a distant or ambient light.";
297
+ };
298
+ readonly direction: {
299
+ readonly kind: "data";
300
+ readonly type: "vec3";
301
+ readonly default: readonly [0, 0, -1];
302
+ readonly description: "Direction the light points. Used by distant and spot.";
303
+ };
304
+ readonly color: {
305
+ readonly kind: "data";
306
+ readonly type: "color";
307
+ readonly default: readonly [1, 1, 1, 1];
308
+ readonly description: "Houdini light_color.";
309
+ };
310
+ readonly intensity: {
311
+ readonly kind: "data";
312
+ readonly type: "float";
313
+ readonly default: 1;
314
+ readonly min: 0;
315
+ readonly description: "Houdini light_intensity.";
316
+ };
317
+ };
318
+ readonly outputs: {
319
+ readonly light: {
320
+ readonly kind: "data";
321
+ readonly type: "light";
322
+ };
323
+ };
324
+ readonly props: {
325
+ readonly type: {
326
+ readonly type: "string";
327
+ readonly default: "point";
328
+ readonly label: "Light Type";
329
+ readonly control: "select";
330
+ readonly options: readonly [{
331
+ readonly value: "point";
332
+ readonly label: "Point";
333
+ }, {
334
+ readonly value: "distant";
335
+ readonly label: "Distant - directional";
336
+ }, {
337
+ readonly value: "ambient";
338
+ readonly label: "Ambient";
339
+ }, {
340
+ readonly value: "spot";
341
+ readonly label: "Spot";
342
+ }];
343
+ readonly description: "Houdini light_type. Distant is what other renderers call directional.";
344
+ };
345
+ readonly coneangle: {
346
+ readonly type: "float";
347
+ readonly default: 45;
348
+ readonly min: 0;
349
+ readonly max: 180;
350
+ readonly label: "Cone Angle";
351
+ readonly description: "Degrees. Spot only.";
352
+ };
353
+ };
354
+ }]];
355
+ export type { Scene };
356
+ //# sourceMappingURL=definitions.d.ts.map
@@ -0,0 +1,224 @@
1
+ import { LIGHT_DEFAULTS, isCamera, isLight, isScene, } from "@field/cascade/contracts";
2
+ /**
3
+ * The one place the scene built-in namespace is written down.
4
+ *
5
+ * `cascade.scene.*` rather than `cascade.geo.*`, and that is a real distinction
6
+ * rather than tidiness: `cascade.geo` is a *medium* — points, primitives,
7
+ * attributes, operations that take geometry and return geometry. A scene is not
8
+ * geometry. It is an assembly of geometry with a viewpoint and lights, and it is
9
+ * the thing you look at rather than the thing you make.
10
+ *
11
+ * The counter-argument, and it is the one this codebase has already accepted
12
+ * once: a namespace with two nodes in it is a promise rather than a structure.
13
+ * `PLAN viewport` says exactly that about `cascade.render.*`, and prefers
14
+ * `cascade.geo.Render` for the render node on those grounds. The difference is
15
+ * that this namespace has a **type** behind it — `scene`, in the core type list
16
+ * — and a type is the thing a namespace is allowed to be named after.
17
+ */
18
+ export const SCENE_NAMESPACE = "cascade.scene";
19
+ export function sceneModuleId(name) {
20
+ return `${SCENE_NAMESPACE}.${name}`;
21
+ }
22
+ /**
23
+ * `cascade.scene.Scene` — geometry, a camera and lights, assembled.
24
+ *
25
+ * Marcus's design, 2026-09-09: *"a 'scene' root node with a variadic input …
26
+ * a 'scene' node with a camera, lights and geometry attached becomes a '3d
27
+ * scene'."*
28
+ *
29
+ * ## Why one variadic `any` input rather than three typed ones
30
+ *
31
+ * Three inputs — a variadic `geometry`, a single `camera`, a variadic `light` —
32
+ * would be better typed, and the connection rules would then refuse a wrong
33
+ * wire before the node ever ran. That is a real loss and it is taken
34
+ * deliberately, because the gesture Marcus described is *attach things to it*,
35
+ * and a single port is the difference between dropping a camera on the node and
36
+ * hunting for which of three stacked ports accepts it. Houdini's own `/obj`
37
+ * network works the same way: everything in it is in the scene by being there,
38
+ * not by being wired to the right socket.
39
+ *
40
+ * What that costs is paid back at execute time rather than ignored. Every input
41
+ * is classified, and **anything unclassifiable throws** naming the port and the
42
+ * value's shape — the alternative is a scene that silently omits what you
43
+ * attached, which is the failure mode this codebase keeps writing rules about.
44
+ * Two cameras throw for the same reason: picking one is a guess, and a guess
45
+ * about which way the camera is pointing is not recoverable by looking at the
46
+ * result.
47
+ *
48
+ * A nested scene is flattened. That makes `Scene` associative — assembling two
49
+ * sub-scenes gives the same result as assembling their contents — and it is
50
+ * what lets a big graph build a scene in parts.
51
+ */
52
+ export const sceneDefinition = {
53
+ apiVersion: 1,
54
+ label: "Scene",
55
+ description: "Geometry, a camera and lights, assembled into a scene. Attach anything to the input.",
56
+ icon: "Box",
57
+ runsOn: "portable",
58
+ inputs: {
59
+ inputs: {
60
+ kind: "data",
61
+ type: "any",
62
+ variadic: true,
63
+ description: "Geometry, one camera, and lights, in any order. Scenes are flattened.",
64
+ },
65
+ },
66
+ outputs: {
67
+ scene: { kind: "data", type: "scene" },
68
+ },
69
+ props: {},
70
+ };
71
+ export function executeScene(context) {
72
+ const geometry = [];
73
+ const lights = [];
74
+ let camera = null;
75
+ let cameraFrom = -1;
76
+ context.inputs.inputs.forEach((value, index) => {
77
+ if (value === undefined || value === null)
78
+ return;
79
+ if (isScene(value)) {
80
+ geometry.push(...value.geometry);
81
+ lights.push(...value.lights);
82
+ if (value.camera !== null) {
83
+ if (camera !== null)
84
+ throw new Error(`cascade.scene.Scene has two cameras, on inputs ${cameraFrom} and ${index}. A scene has one viewpoint; remove one or use a Switch to choose between them.`);
85
+ camera = value.camera;
86
+ cameraFrom = index;
87
+ }
88
+ return;
89
+ }
90
+ if (isCamera(value)) {
91
+ if (camera !== null)
92
+ throw new Error(`cascade.scene.Scene has two cameras, on inputs ${cameraFrom} and ${index}. A scene has one viewpoint; remove one or use a Switch to choose between them.`);
93
+ camera = value;
94
+ cameraFrom = index;
95
+ return;
96
+ }
97
+ if (isLight(value)) {
98
+ lights.push(value);
99
+ return;
100
+ }
101
+ if (typeof value === "object" && value.kind === "geometry") {
102
+ geometry.push(value);
103
+ return;
104
+ }
105
+ throw new Error(`cascade.scene.Scene input ${index} is not geometry, a camera, a light or a scene (received ${describe(value)}). A scene can only hold those.`);
106
+ });
107
+ context.outputs.scene.set({ geometry, camera, lights });
108
+ }
109
+ function describe(value) {
110
+ if (Array.isArray(value))
111
+ return `an array of ${value.length}`;
112
+ if (typeof value === "object" && value !== null) {
113
+ const kind = value.kind;
114
+ return typeof kind === "string" ? `an object of kind "${kind}"` : "an object";
115
+ }
116
+ return `a ${typeof value}`;
117
+ }
118
+ /**
119
+ * `cascade.scene.Light` — Houdini's `/obj/hlight`, as a node.
120
+ *
121
+ * **Nothing shades with these yet**, and that is stated in the description so
122
+ * that a light which appears to do nothing is understood rather than reported
123
+ * as broken. Every piece in the vault today is lines — `cloud-volumes` is a
124
+ * volume, `particle-type` is trails, `cloud-plots` is marks — so there is no
125
+ * surface to light, and inventing a shading model before something needs one is
126
+ * how you get a shading model nobody wants.
127
+ *
128
+ * It exists now because `Scene` accepts lights now. A type that gains a field
129
+ * later invalidates every serialized scene and every node that builds one; the
130
+ * node is the cheap half of a decision already taken in the type.
131
+ */
132
+ export const lightDefinition = {
133
+ apiVersion: 1,
134
+ label: "Light",
135
+ description: "A light, following Houdini's /obj/hlight. Nothing shades with lights yet — the viewport draws wireframe and points.",
136
+ icon: "Lightbulb",
137
+ runsOn: "portable",
138
+ inputs: {
139
+ translate: {
140
+ kind: "data",
141
+ type: "vec3",
142
+ default: [0, 0, 5],
143
+ description: "World position. Ignored for a distant or ambient light.",
144
+ },
145
+ direction: {
146
+ kind: "data",
147
+ type: "vec3",
148
+ default: [0, 0, -1],
149
+ description: "Direction the light points. Used by distant and spot.",
150
+ },
151
+ color: {
152
+ kind: "data",
153
+ type: "color",
154
+ default: [1, 1, 1, 1],
155
+ description: "Houdini light_color.",
156
+ },
157
+ intensity: {
158
+ kind: "data",
159
+ type: "float",
160
+ default: 1,
161
+ min: 0,
162
+ description: "Houdini light_intensity.",
163
+ },
164
+ },
165
+ outputs: {
166
+ light: { kind: "data", type: "light" },
167
+ },
168
+ props: {
169
+ type: {
170
+ type: "string",
171
+ default: "point",
172
+ label: "Light Type",
173
+ control: "select",
174
+ options: [
175
+ { value: "point", label: "Point" },
176
+ { value: "distant", label: "Distant - directional" },
177
+ { value: "ambient", label: "Ambient" },
178
+ { value: "spot", label: "Spot" },
179
+ ],
180
+ description: "Houdini light_type. Distant is what other renderers call directional.",
181
+ },
182
+ coneangle: {
183
+ type: "float",
184
+ default: 45,
185
+ min: 0,
186
+ max: 180,
187
+ label: "Cone Angle",
188
+ description: "Degrees. Spot only.",
189
+ },
190
+ },
191
+ };
192
+ export function executeLight(context) {
193
+ const type = context.props.type;
194
+ context.outputs.light.set({
195
+ type: type === "distant" || type === "ambient" || type === "spot"
196
+ ? type
197
+ : LIGHT_DEFAULTS.type,
198
+ translate: context.inputs.translate ?? LIGHT_DEFAULTS.translate,
199
+ direction: context.inputs.direction ?? LIGHT_DEFAULTS.direction,
200
+ color: context.inputs.color ?? LIGHT_DEFAULTS.color,
201
+ intensity: context.inputs.intensity ?? LIGHT_DEFAULTS.intensity,
202
+ coneangle: context.props.coneangle,
203
+ });
204
+ }
205
+ export const sceneRegistration = {
206
+ kind: "definition-v1",
207
+ moduleId: sceneModuleId("Scene"),
208
+ definition: sceneDefinition,
209
+ loadExecute: async () => executeScene,
210
+ };
211
+ export const lightRegistration = {
212
+ kind: "definition-v1",
213
+ moduleId: sceneModuleId("Light"),
214
+ definition: lightDefinition,
215
+ loadExecute: async () => executeLight,
216
+ };
217
+ export const sceneNodeRegistrations = Object.freeze([sceneRegistration, lightRegistration]);
218
+ /** `[moduleId, definition]` pairs, for a host building a node palette. The same
219
+ * shape `geoNodeDefinitions` and `popNodeDefinitions` have. */
220
+ export const sceneNodeDefinitions = Object.freeze([
221
+ [sceneModuleId("Scene"), sceneDefinition],
222
+ [sceneModuleId("Light"), lightDefinition],
223
+ ]);
224
+ //# sourceMappingURL=definitions.js.map
@@ -0,0 +1,2 @@
1
+ export * from "./definitions.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,2 @@
1
+ export * from "./definitions.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,60 @@
1
+ import type { Camera, Mat4, Vec3 } from "@field/cascade/contracts";
2
+ /** The camera's own axes in world space. */
3
+ export interface CameraBasis {
4
+ /** Where the camera is. */
5
+ readonly eye: Vec3;
6
+ /** Unit vector the camera looks along — its own -Z. */
7
+ readonly forward: Vec3;
8
+ /** Unit vector to the camera's right — its own +X. */
9
+ readonly right: Vec3;
10
+ /** Unit vector for the camera's up — its own +Y. */
11
+ readonly up: Vec3;
12
+ }
13
+ /**
14
+ * The rotation the camera needs to look at a point, as Houdini's look-at does.
15
+ *
16
+ * Returned as degrees in XYZ order so it can be written straight into
17
+ * `camera.rotate` — which is what makes look-at an override of the transform
18
+ * rather than a second, parallel way to orient a camera.
19
+ *
20
+ * **`up` chooses the roll, and only the roll.** Yaw and pitch are fixed the
21
+ * moment a direction is given; all an up vector can still decide is the
22
+ * rotation about that direction. The default `[0, 1, 0]` is Houdini's own
23
+ * `upvector` default and gives roll zero for any horizontal up, which is why
24
+ * every ordinary orbit never needs to think about it.
25
+ *
26
+ * Written first with `roll = 0` and the parameter ignored, which was wrong in
27
+ * the way this codebase keeps finding: the node exposes `up` as an input, so a
28
+ * value set there did nothing at all and nothing said so.
29
+ */
30
+ export declare function lookAtRotation(from: Vec3, to: Vec3, up?: Vec3): Vec3;
31
+ /** The camera's axes, applying Houdini's XYZ rotation order to a camera that
32
+ * looks down -Z at rest. */
33
+ export declare function cameraBasis(camera: Camera): CameraBasis;
34
+ /**
35
+ * The horizontal field of view in degrees, from the lens rather than stored.
36
+ *
37
+ * Houdini's defaults of 50 mm and 41.4214 mm are exactly 45°, which is the
38
+ * check worth having in a test: it is the one value that proves the formula and
39
+ * the defaults agree.
40
+ */
41
+ export declare function horizontalFov(camera: Camera): number;
42
+ /**
43
+ * The VERTICAL field of view in degrees.
44
+ *
45
+ * Houdini does not store a vertical aperture; it derives one from the
46
+ * horizontal aperture, the resolution ratio and the pixel aspect. Which is why
47
+ * `resolution` is on the camera at all — without it there is no vertical angle
48
+ * to be had.
49
+ */
50
+ export declare function verticalFov(camera: Camera): number;
51
+ /** Frame aspect — the shape of the picture, pixel aspect included. Distinct
52
+ * from `camera.aspect`, which is the aspect of one pixel. */
53
+ export declare function frameAspect(camera: Camera): number;
54
+ /** Column-major, GLSL convention, matching `MATRIX_TYPES` in Studio: element 0
55
+ * starts the first COLUMN. */
56
+ export declare function viewMatrix(camera: Camera): Mat4;
57
+ /** Column-major, and orthographic when the camera says so — `orthowidth` is a
58
+ * width in world units, so the height comes from the frame aspect. */
59
+ export declare function projectionMatrix(camera: Camera): Mat4;
60
+ //# sourceMappingURL=index.d.ts.map