@field/cascade 0.4.0 → 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 (110) hide show
  1. package/AGENTS.md +18 -0
  2. package/ARCHITECTURE.md +53 -7
  3. package/CHANGELOG.md +47 -0
  4. package/CONTRIBUTING.md +33 -0
  5. package/DESIGN.md +9 -6
  6. package/README.md +80 -4
  7. package/dist/assets/{CodeEditor-BNOJIDzL.js → CodeEditor-dOrU3aQg.js} +7 -7
  8. package/dist/assets/{CodePanel-2wp2G0Fj.js → CodePanel-Bqy85qen.js} +2 -2
  9. package/dist/assets/{CustomNodeDialog-DNqaVBqA.js → CustomNodeDialog-DkZlOgjs.js} +1 -1
  10. package/dist/assets/{ProjectSettings-DcdYF2E1.js → ProjectSettings-D192mr6g.js} +2 -2
  11. package/dist/assets/{SettingsDialog-D2xVvyiR.js → SettingsDialog-BTeeIE3T.js} +1 -1
  12. package/dist/assets/{VersionHistoryDialog-DW288ykF.js → VersionHistoryDialog-TZQKkn3Y.js} +1 -1
  13. package/dist/assets/{core-B1y38i95.js → core-DwbcGL0o.js} +2 -2
  14. package/dist/assets/{cssMode-BLucnaln.js → cssMode-Dcjn356x.js} +1 -1
  15. package/dist/assets/definitions-C_SY6Bhg.js +1 -0
  16. package/dist/assets/{editor.api-B3Et3Pph.js → editor.api-D7Q83606.js} +1 -1
  17. package/dist/assets/{freemarker2-DVlPoano.js → freemarker2-DCCiHlV6.js} +1 -1
  18. package/dist/assets/geo-CT-wtvaE.js +1 -0
  19. package/dist/assets/{handlebars-B5FZBAMR.js → handlebars-BCxC8xu1.js} +1 -1
  20. package/dist/assets/{html-FodD8KbR.js → html-COwepzSd.js} +1 -1
  21. package/dist/assets/{htmlMode-CsG57VCD.js → htmlMode-Dpg7H0rx.js} +1 -1
  22. package/dist/assets/{image-B3F-nq25.js → image-CMfMMxlC.js} +3 -3
  23. package/dist/assets/index-De1cIZ9N.js +212 -0
  24. package/dist/assets/{index-Y2ock1cw.css → index-ilgbOu6G.css} +1 -1
  25. package/dist/assets/{javascript-B3gonF_H.js → javascript-CSwWxfLr.js} +1 -1
  26. package/dist/assets/{jsonMode-Y3w1ahZg.js → jsonMode-BXRriTpj.js} +1 -1
  27. package/dist/assets/{liquid-CTpWXIYO.js → liquid-Cmkfyogc.js} +1 -1
  28. package/dist/assets/{lspLanguageFeatures-CODBdKEZ.js → lspLanguageFeatures-xH9JbZGO.js} +1 -1
  29. package/dist/assets/{mdx-CDAQJZIz.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-LOIlxJU-.js → python-D4E9jbx7.js} +1 -1
  33. package/dist/assets/{razor-CllPQSHo.js → razor-B3x9cIEM.js} +1 -1
  34. package/dist/assets/{tsMode-CO28ut8t.js → tsMode-dhQYPNMZ.js} +1 -1
  35. package/dist/assets/{typescript-ruU9wKQ_.js → typescript-CITkDxAE.js} +1 -1
  36. package/dist/assets/{workers-Bc1Ywp4-.js → workers-BQvF9bbO.js} +1 -1
  37. package/dist/assets/{xml-DoXP2Dh1.js → xml-DvHlU3A8.js} +1 -1
  38. package/dist/assets/{yaml-D9YPyGiJ.js → yaml-uznsLgs8.js} +1 -1
  39. package/dist/cli/index.js +16227 -12672
  40. package/dist/cli/runtime/io.ts +31 -25
  41. package/dist/contracts/definition.d.ts +21 -2
  42. package/dist/contracts/index.d.ts +2 -0
  43. package/dist/contracts/index.js +2 -0
  44. package/dist/contracts/particles.d.ts +54 -0
  45. package/dist/contracts/particles.js +167 -0
  46. package/dist/contracts/scene.d.ts +129 -0
  47. package/dist/contracts/scene.js +73 -0
  48. package/dist/contracts/schema.js +1 -1
  49. package/dist/contracts/validate.js +4 -2
  50. package/dist/contracts/values.d.ts +3 -2
  51. package/dist/contracts/values.js +9 -0
  52. package/dist/index.html +3 -3
  53. package/dist/io.d.ts +4 -16
  54. package/dist/io.js +26 -8
  55. package/dist/player/app.js +810 -0
  56. package/dist/player/embed.d.ts +4 -0
  57. package/dist/player/embed.js +77 -0
  58. package/dist/player/types.d.ts +35 -0
  59. package/dist/runtime/builtins/core/index.d.ts +3 -2
  60. package/dist/runtime/builtins/core/index.js +6 -2
  61. package/dist/runtime/builtins/core/structural.d.ts +168 -0
  62. package/dist/runtime/builtins/core/structural.js +175 -0
  63. package/dist/runtime/builtins/core/time.d.ts +165 -0
  64. package/dist/runtime/builtins/core/time.js +110 -0
  65. package/dist/runtime/builtins/geo/definitions.d.ts +247 -0
  66. package/dist/runtime/builtins/geo/definitions.js +70 -0
  67. package/dist/runtime/builtins/geo/index.d.ts +2 -1
  68. package/dist/runtime/builtins/geo/index.js +3 -1
  69. package/dist/runtime/builtins/geo/render.d.ts +87 -0
  70. package/dist/runtime/builtins/geo/render.js +87 -0
  71. package/dist/runtime/builtins/geo/svgExport.d.ts +11 -0
  72. package/dist/runtime/builtins/geo/svgExport.js +10 -0
  73. package/dist/runtime/builtins/index.d.ts +5 -2
  74. package/dist/runtime/builtins/index.js +13 -3
  75. package/dist/runtime/builtins/pop/definitions.d.ts +2120 -0
  76. package/dist/runtime/builtins/pop/definitions.js +815 -0
  77. package/dist/runtime/builtins/pop/index.d.ts +2 -0
  78. package/dist/runtime/builtins/pop/index.js +2 -0
  79. package/dist/runtime/builtins/scene/definitions.d.ts +356 -0
  80. package/dist/runtime/builtins/scene/definitions.js +224 -0
  81. package/dist/runtime/builtins/scene/index.d.ts +2 -0
  82. package/dist/runtime/builtins/scene/index.js +2 -0
  83. package/dist/runtime/geometry/render.d.ts +109 -0
  84. package/dist/runtime/geometry/render.js +145 -0
  85. package/dist/runtime/geometry/svg.d.ts +29 -0
  86. package/dist/runtime/geometry/svg.js +11 -5
  87. package/dist/runtime/index.d.ts +1 -0
  88. package/dist/runtime/index.js +1 -0
  89. package/dist/runtime/pop/cache.d.ts +27 -0
  90. package/dist/runtime/pop/cache.js +123 -0
  91. package/dist/runtime/pop/forces.d.ts +202 -0
  92. package/dist/runtime/pop/forces.js +519 -0
  93. package/dist/runtime/pop/index.d.ts +5 -0
  94. package/dist/runtime/pop/index.js +5 -0
  95. package/dist/runtime/pop/state.d.ts +136 -0
  96. package/dist/runtime/pop/state.js +283 -0
  97. package/dist/runtime/pop/trail.d.ts +62 -0
  98. package/dist/runtime/pop/trail.js +142 -0
  99. package/dist/runtime/runtime.js +221 -3
  100. package/dist/runtime/scene/index.d.ts +50 -0
  101. package/dist/runtime/scene/index.js +94 -0
  102. package/dist/studio/panel.d.ts +68 -1
  103. package/doc/HEADLESS_GPU.md +4 -4
  104. package/doc/NODE_REFERENCE.md +372 -1
  105. package/doc/PROJECT_AUTHORING.md +16 -4
  106. package/doc/WEB_PLAYER.md +131 -0
  107. package/package.json +16 -2
  108. package/dist/assets/geo-DhyFdBXX.js +0 -1
  109. package/dist/assets/index-CC6YQw2n.js +0 -212
  110. package/dist/assets/nodeTypeUtils-BBA1JqHx.js +0 -1
@@ -0,0 +1,202 @@
1
+ import type { Geometry } from "@field/cascade/contracts";
2
+ import type { ParticleForce } from "./state.js";
3
+ /**
4
+ * The forces, as plain objects rather than nodes.
5
+ *
6
+ * A force reads the state and returns an acceleration. It never moves
7
+ * anything — integration happens once, in `step`, because two forces that each
8
+ * integrated would disagree about the order of operations and the result would
9
+ * depend on wiring order rather than on physics.
10
+ *
11
+ * Houdini's names for the parameters, per the standing rule.
12
+ */
13
+ /** Constant acceleration. Houdini's POP Force, whose parameter is `force`. */
14
+ export declare function gravity(force: readonly number[]): ParticleForce;
15
+ /**
16
+ * Velocity-opposing drag. Houdini's POP Drag, parameter `airresist`.
17
+ *
18
+ * Returned as an acceleration rather than applied as a multiplier on velocity,
19
+ * which is the same choice as everywhere else here: a drag that scaled velocity
20
+ * directly would be integrating, and its result would then depend on whether it
21
+ * ran before or after a force that also scaled velocity.
22
+ */
23
+ export declare function drag(airresist: number): ParticleForce;
24
+ /**
25
+ * A heading read from a noise field — the first of the four forces the
26
+ * living-identity piece applies.
27
+ *
28
+ * Value noise on a hashed lattice rather than a gradient noise: it is a few
29
+ * lines, it is deterministic from `(seed, cell)` with no state, and at the
30
+ * scales this is used at the difference is not visible. A curl noise belongs in
31
+ * the same file later and wants the same hash.
32
+ *
33
+ * `frequency` is in turns per unit, `amplitude` in units per second squared.
34
+ */
35
+ export declare function noiseField(options: {
36
+ readonly seed: number;
37
+ readonly frequency: number;
38
+ readonly amplitude: number;
39
+ /** Advances the field itself, so the flow evolves. Stated by the caller from
40
+ * the frame, never read from a clock here. */
41
+ readonly evolve?: number;
42
+ }): ParticleForce;
43
+ /**
44
+ * Neighbour separation — the third force, and the expensive one.
45
+ *
46
+ * A uniform grid rebuilt every step, which is what the original does and what
47
+ * `PLAN geometry` says about spatial indexes: they live inside the node that
48
+ * needs them rather than becoming a type. O(n) for the scales here; a system
49
+ * large enough to need something better should say so with a measurement.
50
+ */
51
+ export declare function separation(options: {
52
+ readonly radius: number;
53
+ readonly strength: number;
54
+ }): ParticleForce;
55
+ /**
56
+ * Deterministic value noise. A hash, not a PRNG: the same seed and cell always
57
+ * give the same number, so nothing carries between particles or frames — the
58
+ * property that makes re-simulation from frame zero reproduce exactly.
59
+ */
60
+ export declare function valueNoise(seed: number, x: number, y: number, z?: number): number;
61
+ /**
62
+ * A force read from a scalar field — the second of the four the
63
+ * living-identity piece applies, and the one that makes it say something.
64
+ *
65
+ * The original renders its word to an offscreen canvas, blurs it, reads the
66
+ * pixels back and takes central differences to get a gradient. So the target is
67
+ * a **raster field** rather than a path or an SDF, and that matters: particles
68
+ * are pulled toward wherever the field is bright, whatever drew it. Text is one
69
+ * source; a photograph or a noise image would work identically.
70
+ *
71
+ * The field arrives as luminance already sampled into a grid, because reading
72
+ * an `image` port means decoding a file and that belongs in a node rather than
73
+ * in the arithmetic.
74
+ */
75
+ export declare function fieldForce(options: {
76
+ /** Row-major luminance, 0..1, `width * height` values. */
77
+ readonly field: Float32Array;
78
+ readonly width: number;
79
+ readonly height: number;
80
+ /** The world rectangle the field covers, `[minX, minY, maxX, maxY]`. */
81
+ readonly bounds: readonly [number, number, number, number];
82
+ readonly amplitude: number;
83
+ }): ParticleForce;
84
+ /**
85
+ * Pull particles toward a set of target points. Houdini's POP Attract.
86
+ *
87
+ * The composable half of the field idea. `fieldForce` above takes a sampled
88
+ * raster, which is what the original piece uses; this takes geometry, which is
89
+ * what a graph can wire without any node doing IO — and turning a word, a
90
+ * photograph or a shape into target points is upstream work belonging to
91
+ * whatever node knows about words, photographs or shapes.
92
+ *
93
+ * `radius` is what keeps this affordable: beyond it a target is ignored, so
94
+ * the cost is proportional to nearby targets rather than to all of them. A
95
+ * radius of 0 means every target pulls every particle, which is occasionally
96
+ * what you want and is quadratic.
97
+ */
98
+ export declare function attract(options: {
99
+ readonly targets: ArrayLike<number>;
100
+ readonly targetSize: number;
101
+ readonly targetCount: number;
102
+ readonly amplitude: number;
103
+ readonly radius: number;
104
+ }): ParticleForce;
105
+ /**
106
+ * A flow field carried as geometry: points with a direction on them.
107
+ *
108
+ * Marcus's description of the original, 2026-09-09: *"a delicate balance
109
+ * between a force pulling the particles towards the centre spine of the type
110
+ * and tangentially around their outlines."* Two components of one field, and
111
+ * that sentence is the whole specification.
112
+ *
113
+ * **Why a blurred field gives you a spine.** A hard letterform's gradient
114
+ * points at the nearest edge, so attracting up it pins particles to outlines.
115
+ * Blur the letterform first and each stroke becomes a ridge whose peak runs
116
+ * down its middle — so the gradient points at the *spine*, and the level sets
117
+ * run *along* the outline. Which is why the original blurs its text before
118
+ * reading pixels back: the blur is not softening, it is what turns an outline
119
+ * into a skeleton.
120
+ *
121
+ * So `normal` pulls up the gradient toward the spine, and `tangential` pushes
122
+ * along the perpendicular — around the outline rather than across it. Balance
123
+ * those two and you get the original's behaviour; take either away and you get
124
+ * a clump or a drift.
125
+ *
126
+ * The field arrives as points carrying `N`, because that is a vector field in
127
+ * Cascade's existing vocabulary and it keeps the rasterising upstream in
128
+ * whatever node knows about type. A core force doing image IO is the thing
129
+ * that stopped being pure earlier.
130
+ */
131
+ export declare function flowField(options: {
132
+ /** Field sample positions, flat, `size` components each. */
133
+ readonly positions: ArrayLike<number>;
134
+ readonly positionSize: number;
135
+ /** The gradient at each sample, same count, 2 components — pointing uphill,
136
+ * toward the spine. */
137
+ readonly directions: ArrayLike<number>;
138
+ /** The field's value at each sample, 0..1. Optional; without it `hold` can
139
+ * do nothing, because there is no level to hold. */
140
+ readonly levels?: ArrayLike<number>;
141
+ readonly count: number;
142
+ /** Along `N`, toward the spine. */
143
+ readonly normal: number;
144
+ /** Perpendicular to `N`, around the outline. Sign picks the direction. */
145
+ readonly tangential: number;
146
+ /**
147
+ * Hold this level, rather than climb to the peak.
148
+ *
149
+ * Marcus's sharper description, 2026-09-09: *"the lines flow tangentially
150
+ * around the letters in a bundle … loosely describing the type."* A bundle
151
+ * is a **contour**, and a contour is a level set — so the way to get one is
152
+ * not to pull particles toward the spine but to hold them at a chosen
153
+ * distance from it and let the tangent carry them round.
154
+ *
155
+ * Without this, pure tangential flow does produce a bundle and then every
156
+ * particle converges onto the same level set: measured, and it came out as
157
+ * one line along the baseline of the whole word rather than a contour per
158
+ * letter. `hold` is what keeps a particle on the contour it started near.
159
+ */
160
+ readonly level?: number;
161
+ readonly hold?: number;
162
+ /** Samples beyond this are ignored, and it is also the grid's cell size. */
163
+ readonly radius: number;
164
+ }): ParticleForce;
165
+ /**
166
+ * Houdini's POP convention: a force node **accumulates into a `force`
167
+ * attribute**, and the solver integrates once.
168
+ *
169
+ * Added 2026-09-09, when `pop.Simulate` was rebuilt as a network Marcus can
170
+ * dive into. It is not a refinement — it is the difference between a chain of
171
+ * force nodes that works and one that is silently wrong.
172
+ *
173
+ * The nodes as first written each called `step()` themselves, which integrates.
174
+ * Chain two of them inside a container and time advances **twice per step**:
175
+ * the particles move further than the timestep says, the result changes when
176
+ * you reorder nodes that should commute, and nothing reports an error. Reading
177
+ * that from a picture is close to impossible, which is why the model has to be
178
+ * right rather than tested.
179
+ *
180
+ * So: `accumulateForce` adds an acceleration to the geometry's `force`
181
+ * attribute and moves nothing. `forceAttribute` is the force the solver reads
182
+ * back. The solver integrates and clears it, exactly as a POP network does.
183
+ */
184
+ export declare function accumulateForce(geometry: Geometry, contribution: Float32Array, size: number): Geometry;
185
+ /**
186
+ * The accumulated `force` on a geometry, as a force the solver can sum with the
187
+ * rest.
188
+ *
189
+ * Takes the geometry rather than reading through `ParticleState`, because the
190
+ * state deliberately carries only the attributes the contract makes mandatory —
191
+ * adding a slot for every attribute a force node might invent is how a state
192
+ * object becomes a bag. The solver reads this off the geometry it was handed.
193
+ *
194
+ * Zero when the attribute is absent, which is the correct reading of "no force
195
+ * node ran": a solver with nothing wired into it should hold the particles
196
+ * still rather than refuse to cook.
197
+ */
198
+ export declare function forceAttribute(geometry: Geometry): ParticleForce;
199
+ /** Drop the accumulated force. The solver calls this after integrating, so a
200
+ * step never inherits the previous step's forces. */
201
+ export declare function clearForce(geometry: Geometry): Geometry;
202
+ //# sourceMappingURL=forces.d.ts.map