@womp/spark-agent 5.2.2 → 5.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 (58) hide show
  1. package/README.md +430 -423
  2. package/dist/agent-run.d.ts +1 -1
  3. package/dist/agent-tools.js +30 -12
  4. package/dist/capabilities/geometry.js +14 -14
  5. package/dist/capabilities/materials.d.ts +45 -0
  6. package/dist/capabilities/materials.js +89 -0
  7. package/dist/capabilities/openscad-instructions.js +77 -77
  8. package/dist/capabilities/operations.js +28 -28
  9. package/dist/capabilities/scene.d.ts +21 -0
  10. package/dist/capabilities/scene.js +82 -82
  11. package/dist/capabilities.d.ts +1 -0
  12. package/dist/capabilities.js +1 -0
  13. package/dist/error-diagnostics.d.ts +24 -0
  14. package/dist/error-diagnostics.js +53 -0
  15. package/dist/generated/materials-declarations.d.ts +1 -0
  16. package/dist/generated/materials-declarations.js +3 -0
  17. package/dist/geometry/schemas/backend-schema.json +3393 -3393
  18. package/dist/geometry/schemas/flowir-dialect.json +1023 -1023
  19. package/dist/geometry.js +6 -6
  20. package/dist/kakapo.d.ts +1 -0
  21. package/dist/kakapo.js +1 -0
  22. package/dist/materials/composition.d.ts +64 -0
  23. package/dist/materials/composition.js +121 -0
  24. package/dist/materials/graph.d.ts +79 -0
  25. package/dist/materials/graph.js +335 -0
  26. package/dist/materials/inspection.d.ts +40 -0
  27. package/dist/materials/inspection.js +54 -0
  28. package/dist/materials/recipes.d.ts +12 -0
  29. package/dist/materials/recipes.js +284 -0
  30. package/dist/materials/reference.d.ts +2 -0
  31. package/dist/materials/reference.js +97 -0
  32. package/dist/materials/shaping.d.ts +294 -0
  33. package/dist/materials/shaping.js +184 -0
  34. package/dist/materials/structure.d.ts +2 -0
  35. package/dist/materials/structure.js +12 -0
  36. package/dist/materials/surface-recipes.d.ts +78 -0
  37. package/dist/materials/surface-recipes.js +193 -0
  38. package/dist/materials/types.d.ts +222 -0
  39. package/dist/materials/types.js +1 -0
  40. package/dist/models.d.ts +7 -0
  41. package/dist/models.js +8 -0
  42. package/dist/pricing.js +5 -0
  43. package/dist/prompt.d.ts +1 -1
  44. package/dist/prompt.js +72 -72
  45. package/dist/scene-operations.d.ts +3 -2
  46. package/dist/scene-operations.js +19 -11
  47. package/dist/sdk-geometry-host.js +3 -1
  48. package/dist/sdk-materials-host.d.ts +9 -0
  49. package/dist/sdk-materials-host.js +360 -0
  50. package/dist/sdk-openscad-host.js +3 -1
  51. package/dist/sdk-scene-host.d.ts +6 -1
  52. package/dist/sdk-scene-host.js +19 -3
  53. package/dist/server/fixtures/materials/matte-blue.json +14 -14
  54. package/dist/server/fixtures/materials/polished-copper.json +14 -14
  55. package/dist/server/sqlite-store.js +37 -37
  56. package/dist/server/standalone.js +6 -1
  57. package/dist/ui.d.ts +50 -6
  58. package/package.json +139 -139
@@ -0,0 +1,294 @@
1
+ import { z } from "zod";
2
+ import type { MaterialCatalog, MaterialField, MaterialShapeOptions } from "./types.js";
3
+ type MaterialShapeRequest = MaterialShapeOptions & {
4
+ field: MaterialField;
5
+ };
6
+ export declare const materialShapeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
7
+ kind: z.ZodLiteral<"remap">;
8
+ low: z.ZodNumber;
9
+ high: z.ZodNumber;
10
+ field: z.ZodObject<{
11
+ graph: z.ZodObject<{
12
+ nodes: z.ZodArray<z.ZodObject<{
13
+ op: z.ZodString;
14
+ name: z.ZodOptional<z.ZodString>;
15
+ location: z.ZodOptional<z.ZodObject<{
16
+ x: z.ZodNumber;
17
+ y: z.ZodNumber;
18
+ }, z.core.$strict>>;
19
+ editor_flags: z.ZodOptional<z.ZodNumber>;
20
+ reference_id: z.ZodOptional<z.ZodNumber>;
21
+ out: z.ZodOptional<z.ZodObject<{
22
+ x: z.ZodNumber;
23
+ y: z.ZodNumber;
24
+ z: z.ZodNumber;
25
+ }, z.core.$strict>>;
26
+ }, z.core.$catchall<z.ZodType<import("./types.js").MaterialJson, unknown, z.core.$ZodTypeInternals<import("./types.js").MaterialJson, unknown>>>>>;
27
+ connections: z.ZodArray<z.ZodObject<{
28
+ input_node: z.ZodNumber;
29
+ input_slot: z.ZodNumber;
30
+ output_node: z.ZodNumber;
31
+ output_slot: z.ZodNumber;
32
+ flags: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<2>]>>;
33
+ }, z.core.$strict>>;
34
+ }, z.core.$catchall<z.ZodType<import("./types.js").MaterialJson, unknown, z.core.$ZodTypeInternals<import("./types.js").MaterialJson, unknown>>>>;
35
+ source: z.ZodObject<{
36
+ node: z.ZodNumber;
37
+ slot: z.ZodNumber;
38
+ }, z.core.$strict>;
39
+ }, z.core.$strict>;
40
+ name: z.ZodOptional<z.ZodString>;
41
+ }, z.core.$strict>, z.ZodObject<{
42
+ kind: z.ZodLiteral<"ridge">;
43
+ center: z.ZodNumber;
44
+ width: z.ZodNumber;
45
+ field: z.ZodObject<{
46
+ graph: z.ZodObject<{
47
+ nodes: z.ZodArray<z.ZodObject<{
48
+ op: z.ZodString;
49
+ name: z.ZodOptional<z.ZodString>;
50
+ location: z.ZodOptional<z.ZodObject<{
51
+ x: z.ZodNumber;
52
+ y: z.ZodNumber;
53
+ }, z.core.$strict>>;
54
+ editor_flags: z.ZodOptional<z.ZodNumber>;
55
+ reference_id: z.ZodOptional<z.ZodNumber>;
56
+ out: z.ZodOptional<z.ZodObject<{
57
+ x: z.ZodNumber;
58
+ y: z.ZodNumber;
59
+ z: z.ZodNumber;
60
+ }, z.core.$strict>>;
61
+ }, z.core.$catchall<z.ZodType<import("./types.js").MaterialJson, unknown, z.core.$ZodTypeInternals<import("./types.js").MaterialJson, unknown>>>>>;
62
+ connections: z.ZodArray<z.ZodObject<{
63
+ input_node: z.ZodNumber;
64
+ input_slot: z.ZodNumber;
65
+ output_node: z.ZodNumber;
66
+ output_slot: z.ZodNumber;
67
+ flags: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<2>]>>;
68
+ }, z.core.$strict>>;
69
+ }, z.core.$catchall<z.ZodType<import("./types.js").MaterialJson, unknown, z.core.$ZodTypeInternals<import("./types.js").MaterialJson, unknown>>>>;
70
+ source: z.ZodObject<{
71
+ node: z.ZodNumber;
72
+ slot: z.ZodNumber;
73
+ }, z.core.$strict>;
74
+ }, z.core.$strict>;
75
+ name: z.ZodOptional<z.ZodString>;
76
+ }, z.core.$strict>, z.ZodObject<{
77
+ kind: z.ZodLiteral<"periodic">;
78
+ coordinates: z.ZodOptional<z.ZodNumber>;
79
+ axis: z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>]>;
80
+ sharpness: z.ZodNumber;
81
+ field: z.ZodObject<{
82
+ graph: z.ZodObject<{
83
+ nodes: z.ZodArray<z.ZodObject<{
84
+ op: z.ZodString;
85
+ name: z.ZodOptional<z.ZodString>;
86
+ location: z.ZodOptional<z.ZodObject<{
87
+ x: z.ZodNumber;
88
+ y: z.ZodNumber;
89
+ }, z.core.$strict>>;
90
+ editor_flags: z.ZodOptional<z.ZodNumber>;
91
+ reference_id: z.ZodOptional<z.ZodNumber>;
92
+ out: z.ZodOptional<z.ZodObject<{
93
+ x: z.ZodNumber;
94
+ y: z.ZodNumber;
95
+ z: z.ZodNumber;
96
+ }, z.core.$strict>>;
97
+ }, z.core.$catchall<z.ZodType<import("./types.js").MaterialJson, unknown, z.core.$ZodTypeInternals<import("./types.js").MaterialJson, unknown>>>>>;
98
+ connections: z.ZodArray<z.ZodObject<{
99
+ input_node: z.ZodNumber;
100
+ input_slot: z.ZodNumber;
101
+ output_node: z.ZodNumber;
102
+ output_slot: z.ZodNumber;
103
+ flags: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<2>]>>;
104
+ }, z.core.$strict>>;
105
+ }, z.core.$catchall<z.ZodType<import("./types.js").MaterialJson, unknown, z.core.$ZodTypeInternals<import("./types.js").MaterialJson, unknown>>>>;
106
+ source: z.ZodObject<{
107
+ node: z.ZodNumber;
108
+ slot: z.ZodNumber;
109
+ }, z.core.$strict>;
110
+ }, z.core.$strict>;
111
+ name: z.ZodOptional<z.ZodString>;
112
+ }, z.core.$strict>, z.ZodObject<{
113
+ kind: z.ZodLiteral<"warp">;
114
+ coordinates: z.ZodOptional<z.ZodNumber>;
115
+ frequency: z.ZodNumber;
116
+ amplitude: z.ZodNumber;
117
+ field: z.ZodObject<{
118
+ graph: z.ZodObject<{
119
+ nodes: z.ZodArray<z.ZodObject<{
120
+ op: z.ZodString;
121
+ name: z.ZodOptional<z.ZodString>;
122
+ location: z.ZodOptional<z.ZodObject<{
123
+ x: z.ZodNumber;
124
+ y: z.ZodNumber;
125
+ }, z.core.$strict>>;
126
+ editor_flags: z.ZodOptional<z.ZodNumber>;
127
+ reference_id: z.ZodOptional<z.ZodNumber>;
128
+ out: z.ZodOptional<z.ZodObject<{
129
+ x: z.ZodNumber;
130
+ y: z.ZodNumber;
131
+ z: z.ZodNumber;
132
+ }, z.core.$strict>>;
133
+ }, z.core.$catchall<z.ZodType<import("./types.js").MaterialJson, unknown, z.core.$ZodTypeInternals<import("./types.js").MaterialJson, unknown>>>>>;
134
+ connections: z.ZodArray<z.ZodObject<{
135
+ input_node: z.ZodNumber;
136
+ input_slot: z.ZodNumber;
137
+ output_node: z.ZodNumber;
138
+ output_slot: z.ZodNumber;
139
+ flags: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<2>]>>;
140
+ }, z.core.$strict>>;
141
+ }, z.core.$catchall<z.ZodType<import("./types.js").MaterialJson, unknown, z.core.$ZodTypeInternals<import("./types.js").MaterialJson, unknown>>>>;
142
+ source: z.ZodObject<{
143
+ node: z.ZodNumber;
144
+ slot: z.ZodNumber;
145
+ }, z.core.$strict>;
146
+ }, z.core.$strict>;
147
+ name: z.ZodOptional<z.ZodString>;
148
+ }, z.core.$strict>, z.ZodObject<{
149
+ kind: z.ZodLiteral<"palette">;
150
+ mode: z.ZodEnum<{
151
+ Constant: "Constant";
152
+ Linear: "Linear";
153
+ Smooth: "Smooth";
154
+ }>;
155
+ stops: z.ZodArray<z.ZodObject<{
156
+ position: z.ZodNumber;
157
+ color: z.ZodObject<{
158
+ x: z.ZodNumber;
159
+ y: z.ZodNumber;
160
+ z: z.ZodNumber;
161
+ }, z.core.$strict>;
162
+ }, z.core.$strict>>;
163
+ field: z.ZodObject<{
164
+ graph: z.ZodObject<{
165
+ nodes: z.ZodArray<z.ZodObject<{
166
+ op: z.ZodString;
167
+ name: z.ZodOptional<z.ZodString>;
168
+ location: z.ZodOptional<z.ZodObject<{
169
+ x: z.ZodNumber;
170
+ y: z.ZodNumber;
171
+ }, z.core.$strict>>;
172
+ editor_flags: z.ZodOptional<z.ZodNumber>;
173
+ reference_id: z.ZodOptional<z.ZodNumber>;
174
+ out: z.ZodOptional<z.ZodObject<{
175
+ x: z.ZodNumber;
176
+ y: z.ZodNumber;
177
+ z: z.ZodNumber;
178
+ }, z.core.$strict>>;
179
+ }, z.core.$catchall<z.ZodType<import("./types.js").MaterialJson, unknown, z.core.$ZodTypeInternals<import("./types.js").MaterialJson, unknown>>>>>;
180
+ connections: z.ZodArray<z.ZodObject<{
181
+ input_node: z.ZodNumber;
182
+ input_slot: z.ZodNumber;
183
+ output_node: z.ZodNumber;
184
+ output_slot: z.ZodNumber;
185
+ flags: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<2>]>>;
186
+ }, z.core.$strict>>;
187
+ }, z.core.$catchall<z.ZodType<import("./types.js").MaterialJson, unknown, z.core.$ZodTypeInternals<import("./types.js").MaterialJson, unknown>>>>;
188
+ source: z.ZodObject<{
189
+ node: z.ZodNumber;
190
+ slot: z.ZodNumber;
191
+ }, z.core.$strict>;
192
+ }, z.core.$strict>;
193
+ name: z.ZodOptional<z.ZodString>;
194
+ }, z.core.$strict>, z.ZodObject<{
195
+ kind: z.ZodLiteral<"blend">;
196
+ overlay: z.ZodUnion<readonly [z.ZodObject<{
197
+ graph: z.ZodObject<{
198
+ nodes: z.ZodArray<z.ZodObject<{
199
+ op: z.ZodString;
200
+ name: z.ZodOptional<z.ZodString>;
201
+ location: z.ZodOptional<z.ZodObject<{
202
+ x: z.ZodNumber;
203
+ y: z.ZodNumber;
204
+ }, z.core.$strict>>;
205
+ editor_flags: z.ZodOptional<z.ZodNumber>;
206
+ reference_id: z.ZodOptional<z.ZodNumber>;
207
+ out: z.ZodOptional<z.ZodObject<{
208
+ x: z.ZodNumber;
209
+ y: z.ZodNumber;
210
+ z: z.ZodNumber;
211
+ }, z.core.$strict>>;
212
+ }, z.core.$catchall<z.ZodType<import("./types.js").MaterialJson, unknown, z.core.$ZodTypeInternals<import("./types.js").MaterialJson, unknown>>>>>;
213
+ connections: z.ZodArray<z.ZodObject<{
214
+ input_node: z.ZodNumber;
215
+ input_slot: z.ZodNumber;
216
+ output_node: z.ZodNumber;
217
+ output_slot: z.ZodNumber;
218
+ flags: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<2>]>>;
219
+ }, z.core.$strict>>;
220
+ }, z.core.$catchall<z.ZodType<import("./types.js").MaterialJson, unknown, z.core.$ZodTypeInternals<import("./types.js").MaterialJson, unknown>>>>;
221
+ source: z.ZodObject<{
222
+ node: z.ZodNumber;
223
+ slot: z.ZodNumber;
224
+ }, z.core.$strict>;
225
+ }, z.core.$strict>, z.ZodObject<{
226
+ x: z.ZodNumber;
227
+ y: z.ZodNumber;
228
+ z: z.ZodNumber;
229
+ }, z.core.$strict>]>;
230
+ mask: z.ZodUnion<readonly [z.ZodObject<{
231
+ graph: z.ZodObject<{
232
+ nodes: z.ZodArray<z.ZodObject<{
233
+ op: z.ZodString;
234
+ name: z.ZodOptional<z.ZodString>;
235
+ location: z.ZodOptional<z.ZodObject<{
236
+ x: z.ZodNumber;
237
+ y: z.ZodNumber;
238
+ }, z.core.$strict>>;
239
+ editor_flags: z.ZodOptional<z.ZodNumber>;
240
+ reference_id: z.ZodOptional<z.ZodNumber>;
241
+ out: z.ZodOptional<z.ZodObject<{
242
+ x: z.ZodNumber;
243
+ y: z.ZodNumber;
244
+ z: z.ZodNumber;
245
+ }, z.core.$strict>>;
246
+ }, z.core.$catchall<z.ZodType<import("./types.js").MaterialJson, unknown, z.core.$ZodTypeInternals<import("./types.js").MaterialJson, unknown>>>>>;
247
+ connections: z.ZodArray<z.ZodObject<{
248
+ input_node: z.ZodNumber;
249
+ input_slot: z.ZodNumber;
250
+ output_node: z.ZodNumber;
251
+ output_slot: z.ZodNumber;
252
+ flags: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<2>]>>;
253
+ }, z.core.$strict>>;
254
+ }, z.core.$catchall<z.ZodType<import("./types.js").MaterialJson, unknown, z.core.$ZodTypeInternals<import("./types.js").MaterialJson, unknown>>>>;
255
+ source: z.ZodObject<{
256
+ node: z.ZodNumber;
257
+ slot: z.ZodNumber;
258
+ }, z.core.$strict>;
259
+ }, z.core.$strict>, z.ZodNumber]>;
260
+ field: z.ZodObject<{
261
+ graph: z.ZodObject<{
262
+ nodes: z.ZodArray<z.ZodObject<{
263
+ op: z.ZodString;
264
+ name: z.ZodOptional<z.ZodString>;
265
+ location: z.ZodOptional<z.ZodObject<{
266
+ x: z.ZodNumber;
267
+ y: z.ZodNumber;
268
+ }, z.core.$strict>>;
269
+ editor_flags: z.ZodOptional<z.ZodNumber>;
270
+ reference_id: z.ZodOptional<z.ZodNumber>;
271
+ out: z.ZodOptional<z.ZodObject<{
272
+ x: z.ZodNumber;
273
+ y: z.ZodNumber;
274
+ z: z.ZodNumber;
275
+ }, z.core.$strict>>;
276
+ }, z.core.$catchall<z.ZodType<import("./types.js").MaterialJson, unknown, z.core.$ZodTypeInternals<import("./types.js").MaterialJson, unknown>>>>>;
277
+ connections: z.ZodArray<z.ZodObject<{
278
+ input_node: z.ZodNumber;
279
+ input_slot: z.ZodNumber;
280
+ output_node: z.ZodNumber;
281
+ output_slot: z.ZodNumber;
282
+ flags: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<2>]>>;
283
+ }, z.core.$strict>>;
284
+ }, z.core.$catchall<z.ZodType<import("./types.js").MaterialJson, unknown, z.core.$ZodTypeInternals<import("./types.js").MaterialJson, unknown>>>>;
285
+ source: z.ZodObject<{
286
+ node: z.ZodNumber;
287
+ slot: z.ZodNumber;
288
+ }, z.core.$strict>;
289
+ }, z.core.$strict>;
290
+ name: z.ZodOptional<z.ZodString>;
291
+ }, z.core.$strict>], "kind">;
292
+ export declare function materialShapeOperations(options: MaterialShapeRequest): string[];
293
+ export declare function makeMaterialShape(options: MaterialShapeRequest, catalogue: MaterialCatalog): MaterialField;
294
+ export {};
@@ -0,0 +1,184 @@
1
+ import { z } from "zod";
2
+ import { graphSchema, makeMaterialNode, pruneMaterialGraph, validateMaterialGraph } from "./graph.js";
3
+ const number = z.number().finite();
4
+ const coordinate = z.number().int().nonnegative().optional();
5
+ const field = z.object({ graph: graphSchema, source: z.object({ node: z.number().int().nonnegative(), slot: z.number().int().min(0).max(19) }).strict() }).strict();
6
+ const color = z.object({ x: number.min(0).max(1), y: number.min(0).max(1), z: number.min(0).max(1) }).strict();
7
+ const base = { field, name: z.string().min(1).optional() };
8
+ export const materialShapeSchema = z.discriminatedUnion("kind", [
9
+ z.object({ ...base, kind: z.literal("remap"), low: number, high: number }).strict(),
10
+ z.object({ ...base, kind: z.literal("ridge"), center: number, width: number.positive() }).strict(),
11
+ z.object({ ...base, kind: z.literal("periodic"), coordinates: coordinate, axis: z.union([z.literal(0), z.literal(1), z.literal(2)]), sharpness: number.positive() }).strict(),
12
+ z.object({ ...base, kind: z.literal("warp"), coordinates: coordinate, frequency: number.positive(), amplitude: number }).strict(),
13
+ z.object({ ...base, kind: z.literal("palette"), mode: z.enum(["Constant", "Linear", "Smooth"]), stops: z.array(z.object({ position: number.min(0).max(1), color: z.object({ x: number.min(0).max(1), y: number.min(0).max(1), z: number.min(0).max(1) }).strict() }).strict()).min(2) }).strict(),
14
+ z.object({ ...base, kind: z.literal("blend"), overlay: z.union([field, color]), mask: z.union([field, number.min(0).max(1)]) }).strict(),
15
+ ]).superRefine((options, context) => {
16
+ if (options.kind === "remap" && options.low === options.high)
17
+ context.addIssue({ code: "custom", message: "Mask endpoints must differ" });
18
+ const fields = [options.field, ...(options.kind === "blend" ? [...("graph" in options.overlay ? [options.overlay] : []), ...(typeof options.mask === "object" ? [options.mask] : [])] : [])];
19
+ for (const field of fields) {
20
+ if (!field.graph.nodes[field.source.node])
21
+ context.addIssue({ code: "custom", message: "Field output node does not exist" });
22
+ if (field.graph.nodes.some(node => node.op.startsWith("Output")))
23
+ context.addIssue({ code: "custom", message: "Fields must not contain surface output nodes" });
24
+ }
25
+ });
26
+ export function materialShapeOperations(options) {
27
+ const fields = [options.field, ...(options.kind === "blend" ? [...("graph" in options.overlay ? [options.overlay] : []), ...(typeof options.mask === "object" ? [options.mask] : [])] : [])];
28
+ return [...new Set([...fields.flatMap(field => field.graph.nodes.map(node => node.op)), "OutputMaterial", "Constant", "Math", "GetComponent",
29
+ ...(options.kind === "blend" ? ["MathLerp"] : []),
30
+ ...(options.kind === "palette" ? ["ColorRamp"] : []),
31
+ ...(options.kind === "periodic" ? ["GetComponent", "MathLerp"] : []),
32
+ ...(options.kind === "warp" ? ["Mapping", "NoiseFBM"] : []),
33
+ ])];
34
+ }
35
+ export function makeMaterialShape(options, catalogue) {
36
+ const graph = structuredClone(options.field.graph);
37
+ const inputOutput = catalogue.nodes[graph.nodes[options.field.source.node].op]?.outputs[options.field.source.slot];
38
+ if (!inputOutput || !["Float", "Float3", "Color"].includes(inputOutput.type))
39
+ throw new Error("Field requires a numeric output port");
40
+ const label = options.name ?? options.kind + "." + graph.nodes.length;
41
+ const selector = (op, slot, value) => {
42
+ const index = catalogue.nodes[op]?.inputs[slot]?.values?.indexOf(value) ?? -1;
43
+ if (index < 0)
44
+ throw new Error(op + " requires option " + value);
45
+ return index;
46
+ };
47
+ const add = (op, inputs, name) => {
48
+ graph.nodes.push(makeMaterialNode({ op, inputs, name: label + "." + name }));
49
+ return { node: graph.nodes.length - 1, slot: 0 };
50
+ };
51
+ const wire = (source, target, port) => graph.connections.push({ input_node: source.node, input_slot: source.slot, output_node: target.node, output_slot: port, flags: 0 });
52
+ const math = (operation, inputs, name) => add("Math", { 0: selector("Math", 0, operation), 3: 0, ...inputs }, name);
53
+ const control = (value, name) => {
54
+ const constant = { ...add("Constant", { 1: value, 3: selector("Constant", 3, "Expose X") }, name), slot: 1 };
55
+ const scalar = add("GetComponent", { 1: 0 }, `${name}.scalar`);
56
+ wire(constant, scalar, 0);
57
+ return scalar;
58
+ };
59
+ const coordinateSource = () => {
60
+ const index = "coordinates" in options && options.coordinates !== undefined ? options.coordinates : graph.nodes.findIndex(node => node.name === "Feature size in selected coordinates");
61
+ const output = catalogue.nodes[graph.nodes[index]?.op ?? ""]?.outputs[0];
62
+ if (!output || !["Float3", "Color"].includes(output.type))
63
+ throw new Error("Select an existing vector coordinate node; custom fields require coordinates");
64
+ return { node: index, slot: 0 };
65
+ };
66
+ let source = options.field.source;
67
+ if (options.kind === "remap" || options.kind === "ridge") {
68
+ const center = control(options.kind === "remap" ? options.low : options.center, "center");
69
+ const shifted = math("Subtract", {}, "offset");
70
+ wire(source, shifted, 1);
71
+ wire(center, shifted, 2);
72
+ let distance = shifted;
73
+ if (options.kind === "ridge") {
74
+ distance = math("Absolute Value", {}, "distance");
75
+ wire(shifted, distance, 1);
76
+ }
77
+ const scaled = math("Divide", { 3: 1 }, "normalized");
78
+ wire(distance, scaled, 1);
79
+ if (options.kind === "ridge")
80
+ wire(control(options.width, "width"), scaled, 2);
81
+ else {
82
+ const width = math("Subtract", {}, "range");
83
+ wire(control(options.high, "high"), width, 1);
84
+ wire(center, width, 2);
85
+ wire(width, scaled, 2);
86
+ }
87
+ source = scaled;
88
+ if (options.kind === "ridge") {
89
+ source = math("Subtract", { 1: 1, 3: 1 }, "ridge");
90
+ wire(scaled, source, 2);
91
+ }
92
+ }
93
+ else if (options.kind === "periodic") {
94
+ const coordinates = coordinateSource();
95
+ const component = add("GetComponent", { 1: options.axis }, "axis");
96
+ wire(coordinates, component, 0);
97
+ const phase = math("Mulitiply", { 2: 2 * Math.PI }, "phase");
98
+ wire(component, phase, 1);
99
+ const sine = math("Sine", {}, "sine");
100
+ wire(phase, sine, 1);
101
+ const positive = add("MathLerp", { 0: 0.5, 1: 1 }, "positive");
102
+ wire(sine, positive, 2);
103
+ source = math("Power", { 3: 1 }, "profile");
104
+ wire(positive, source, 1);
105
+ wire(control(options.sharpness, "sharpness"), source, 2);
106
+ }
107
+ else if (options.kind === "warp") {
108
+ const coordinates = coordinateSource();
109
+ const consumers = graph.connections.filter(edge => edge.input_node === coordinates.node && edge.input_slot === coordinates.slot);
110
+ const frequency = control(options.frequency, "frequency"), amplitude = control(options.amplitude, "amplitude");
111
+ const components = [0, 1, 2].map(axis => {
112
+ const shifted = add("Mapping", { 1: { x: 17 * (axis + 1), y: 31 * (axis + 1), z: 47 * (axis + 1) }, 2: 0, 3: 1 }, "seed");
113
+ wire(coordinates, shifted, 0);
114
+ const noise = add("NoiseFBM", { 2: 3, 3: 1, 4: 0 }, "variation");
115
+ wire(shifted, noise, 0);
116
+ wire(frequency, noise, 1);
117
+ const centered = math("Subtract", { 2: 0.5 }, "centered");
118
+ wire(noise, centered, 1);
119
+ const scaled = math("Mulitiply", {}, "strength");
120
+ wire(centered, scaled, 1);
121
+ wire(amplitude, scaled, 2);
122
+ const basis = { x: axis === 0 ? 1 : 0, y: axis === 1 ? 1 : 0, z: axis === 2 ? 1 : 0 };
123
+ const component = math("Mulitiply", { 2: basis }, "component");
124
+ wire(scaled, component, 1);
125
+ return component;
126
+ });
127
+ const xy = math("Add", {}, "xy");
128
+ wire(components[0], xy, 1);
129
+ wire(components[1], xy, 2);
130
+ const xyz = math("Add", {}, "xyz");
131
+ wire(xy, xyz, 1);
132
+ wire(components[2], xyz, 2);
133
+ const warped = add("Mapping", { 2: 0, 3: 1 }, "coordinates");
134
+ wire(coordinates, warped, 0);
135
+ wire(xyz, warped, 1);
136
+ consumers.forEach(edge => { edge.input_node = warped.node; edge.input_slot = 0; });
137
+ if (source.node === coordinates.node && source.slot === coordinates.slot)
138
+ source = warped;
139
+ }
140
+ else if (options.kind === "palette") {
141
+ const palette = graph.nodes.length;
142
+ graph.nodes.push(makeMaterialNode({ op: "ColorRamp", name: label + ".palette", inputs: { 3: selector("ColorRamp", 3, options.mode) }, ramp: options.stops }));
143
+ const output = { node: palette, slot: 0 };
144
+ wire(source, output, 0);
145
+ source = output;
146
+ }
147
+ else {
148
+ // Each branch owns independent controls, even when the same source is reused.
149
+ graph.nodes.forEach(node => { if (node.op === "Constant")
150
+ delete node.reference_id; });
151
+ const append = (field, branch) => {
152
+ const output = catalogue.nodes[field.graph.nodes[field.source.node].op]?.outputs[field.source.slot];
153
+ if (!output || !["Float", "Float3", "Color"].includes(output.type))
154
+ throw new Error("Field requires a numeric output port");
155
+ const offset = graph.nodes.length;
156
+ graph.nodes.push(...structuredClone(field.graph.nodes).map(node => {
157
+ if (node.op === "Constant")
158
+ delete node.reference_id;
159
+ node.name = label + "." + branch + "." + (node.name ?? node.op);
160
+ return node;
161
+ }));
162
+ graph.connections.push(...field.graph.connections.map(edge => ({ ...edge, input_node: edge.input_node + offset, output_node: edge.output_node + offset })));
163
+ return { node: field.source.node + offset, slot: field.source.slot };
164
+ };
165
+ const overlay = "graph" in options.overlay ? append(options.overlay, "overlay") : { ...add("Constant", { 2: options.overlay, 3: selector("Constant", 3, "Expose Color") }, "color"), slot: 2 };
166
+ let mask = typeof options.mask === "number" ? control(options.mask, "amount") : append(options.mask, "mask");
167
+ const scalar = add("GetComponent", { 1: 0 }, "mask.scalar");
168
+ wire(mask, scalar, 0);
169
+ mask = math("Add", { 2: 0, 3: 1 }, "mask.clamped");
170
+ wire(scalar, mask, 1);
171
+ const blended = add("MathLerp", {}, "blend");
172
+ wire(source, blended, 0);
173
+ wire(overlay, blended, 1);
174
+ wire(mask, blended, 2);
175
+ source = blended;
176
+ }
177
+ // Validate terminal-free fields through a temporary output, then prune unused generators.
178
+ const output = add("OutputMaterial", {}, "validation");
179
+ wire(source, output, 0);
180
+ const validated = pruneMaterialGraph(validateMaterialGraph(graph, catalogue.nodes));
181
+ const terminal = validated.nodes.length - 1;
182
+ const edge = validated.connections.find(edge => edge.output_node === terminal);
183
+ return { graph: { ...validated, nodes: validated.nodes.slice(0, terminal), connections: validated.connections.filter(edge => edge.output_node !== terminal) }, source: { node: edge.input_node, slot: edge.input_slot } };
184
+ }
@@ -0,0 +1,2 @@
1
+ export declare const MATERIALS_STRUCTURE_GUIDANCE = "shape(field, options) constructs generic remap, ridge, periodic, warp, palette and masked-blend fields. Name features for later editing. Remap endpoints, ridge center/width, periodic sharpness and warp frequency/amplitude are exposed Constants; palette stops remain graph data. Remap/ridge/periodic use analytic math. For a smooth nonlinear scalar profile, use the palette shape with mode:'Smooth' and grayscale stops (equal RGB components); compose reads X for relief or masks. Use this when a simple remap or ridge cannot reproduce the intended shape.\nshape(base,{kind:'blend',overlay,mask,name}) layers an RGB/scalar field or linear RGB literal over the base using a field mask or numeric amount. Zero mask retains the base; one selects the overlay. Field masks use clamped X broadcast to RGB. This helper resolves MathLerp internally; literal overlay colors and amounts become editable Constants. Build independent palettes and masks, then chain blend calls for feature-specific colors in one construction batch.\nPeriodic and warp coordinates default to the field's feature-size Mapping. Custom fields require an explicit vector coordinate node. Warp frequency/amplitude are relative to those coordinates; preserve measured feature size. Build periodic profiles, warp their coordinates, then form ridges/masks. Resolve indices in each returned graph because unused nodes are pruned. Independent XYZ warp avoids diagonal distortion.\nA generic noise layer counts only when it reproduces the requested shape. Construct defining boundaries, strands and crossings explicitly. Palette vector fields preserve RGB with compose color low:{x:0,y:0,z:0}, high:{x:1,y:1,z:1}, weight 1; use separate scalar fields for finish/relief and combine heights before deriving normals once. Composed fields have independent Constant handles.\nFor custom graphs, node({inputs}) keys are numeric input-slot indices, never displayed port titles. Arithmetic uses op:'Math', with its operation selected by literal input 0; this engine's multiplication selector is 2 (label 'Mulitiply'). Check the current descriptor for other modes. Use exact catalogue operation names. When removing a graph node, remove and reindex its connections instead of leaving an unsupported placeholder.";
2
+ export declare const MATERIALS_QUALITY_GUIDANCE = "Create materials with the highest achievable fidelity, quality and detail resolution supported by these capabilities.\nFor every new material: inspect target dimensions and ancestor transforms, then create and inspect an isolated base-color grid probe BEFORE building final surface structure. Use object-space featureSize:1: major lines every 1 unit and lower-contrast minor lines every 0.1 unit together, with visible line colors and shallow height relief. Assess spacing, stretch, orientation and relief on relevant faces. Correct unintended coordinate distortion without changing geometry or transforms. Replace the probe completely; preserve other material users and assignments. World-space field sizes are physical millimeters; object-space sizes follow carrier coordinates and transforms. Non-uniform dimensions and ancestor transforms affect mapping. Keep physical feature size consistent and all channels aligned; distinguish intended directional anatomy from smeared stretching.\nBefore construction, decompose the description/reference into broad variation, defining structure and fine grain. Record each component's relative scale, shape, direction, distribution, edge character and contrast, and its color/roughness/relief contributions. Apply text overrides while preserving remaining reference features. Distinguish reference illumination from intrinsic structure. Give microstructure independent scale and strength; superimpose components without dominant motifs obscuring it.\nBuild all planned components and finish channels together using field, shape and compose. Their supported operations and selectors are resolved internally; construct masks, palettes and blends directly from these helpers. For textured surfaces, apply the tested readability adjustment in the first construction: bump scale \u00D71.25, palette saturation \u00D71.15, palette lightness \u00D70.95 and scalar feature contrast \u00D71.15 around 0.5, clamped to 0..1. Apply color factors in the known display-color encoding, then decode back to linear RGB; native HSL inputs are additive offsets. Incorporate this once into chosen base controls, without an extra material/render pass or compounding it during refinement. Explicit flat, muted or pale requests take precedence; retain reference hues, defining features and the requested finish. These factors are a starting adjustment, not universal optimum values. Keep defining height and fine-noise height independently weighted; increasing all bump together can erase coherent highlights and change gloss into a matte finish. Relief responds to height slope: finer spacing, sharper edges and more noise octaves can strengthen normals at unchanged scale. Calibrated ranges describe particular fields, not quality ceilings or universal strengths.\nAdjust palette saturation/lightness separately from feature coverage and edge contrast; use bounded scalar remaps before palettes or masked blends, checking clipped colors and changed coverage. Keep intrinsic color separate from illumination; preserve natural low-saturation features and coherent highlights. Author linear RGB; decode reference/display colors and preserve already-linear colors and scalar channels. Use roughness variation for finish breakup and signed height profiles for actual ridges/cavities; a dark spot alone does not establish a pit.\nInspect complementary overall, close and grazing views together under the existing scene lighting, geometry and render settings; adjust viewpoints for inspection. Match tonal contrast and reflectivity through highlight width, strength and breakup. Confirm every planned feature remains visibly resolved: a fine-noise node does not prove fine grain. Check that defining relief reads through lighting at the intended viewing distance, rather than only through its color mask. Subpixel features cannot remain individually resolved by increasing strength or octaves. If relief destroys the requested sheen, rebalance the offending feature's height/frequency before compensating roughness.\nFor each refinement, name the largest visible mismatch and the expected correction. Wrong topology/shape needs construction changes; wrong spacing, color, roughness or relief needs the corresponding parameter changes. Use known construction inputs and returned control handles to apply the correction in that batch. Read graphs when editing a previously unknown material or recovering unavailable/stale handles. Adjust related controls together and inspect once. Compare each candidate with the best inspected version at the same views; restore the better graph if a change weakens a defining feature. If tuning fails to improve a mismatch, change its construction rather than repeating strength changes. Continue while a specific, supported correction is likely to improve a substantial mismatch; stop when the result meets the request or further edits cease to help. Do not end with an uninspected edit. Print only relevant IDs, controls and observations; retain full graphs locally for edits.";
@@ -0,0 +1,12 @@
1
+ export const MATERIALS_STRUCTURE_GUIDANCE = `shape(field, options) constructs generic remap, ridge, periodic, warp, palette and masked-blend fields. Name features for later editing. Remap endpoints, ridge center/width, periodic sharpness and warp frequency/amplitude are exposed Constants; palette stops remain graph data. Remap/ridge/periodic use analytic math. For a smooth nonlinear scalar profile, use the palette shape with mode:'Smooth' and grayscale stops (equal RGB components); compose reads X for relief or masks. Use this when a simple remap or ridge cannot reproduce the intended shape.
2
+ shape(base,{kind:'blend',overlay,mask,name}) layers an RGB/scalar field or linear RGB literal over the base using a field mask or numeric amount. Zero mask retains the base; one selects the overlay. Field masks use clamped X broadcast to RGB. This helper resolves MathLerp internally; literal overlay colors and amounts become editable Constants. Build independent palettes and masks, then chain blend calls for feature-specific colors in one construction batch.
3
+ Periodic and warp coordinates default to the field's feature-size Mapping. Custom fields require an explicit vector coordinate node. Warp frequency/amplitude are relative to those coordinates; preserve measured feature size. Build periodic profiles, warp their coordinates, then form ridges/masks. Resolve indices in each returned graph because unused nodes are pruned. Independent XYZ warp avoids diagonal distortion.
4
+ A generic noise layer counts only when it reproduces the requested shape. Construct defining boundaries, strands and crossings explicitly. Palette vector fields preserve RGB with compose color low:{x:0,y:0,z:0}, high:{x:1,y:1,z:1}, weight 1; use separate scalar fields for finish/relief and combine heights before deriving normals once. Composed fields have independent Constant handles.
5
+ For custom graphs, node({inputs}) keys are numeric input-slot indices, never displayed port titles. Arithmetic uses op:'Math', with its operation selected by literal input 0; this engine's multiplication selector is 2 (label 'Mulitiply'). Check the current descriptor for other modes. Use exact catalogue operation names. When removing a graph node, remove and reindex its connections instead of leaving an unsupported placeholder.`;
6
+ export const MATERIALS_QUALITY_GUIDANCE = `Create materials with the highest achievable fidelity, quality and detail resolution supported by these capabilities.
7
+ For every new material: inspect target dimensions and ancestor transforms, then create and inspect an isolated base-color grid probe BEFORE building final surface structure. Use object-space featureSize:1: major lines every 1 unit and lower-contrast minor lines every 0.1 unit together, with visible line colors and shallow height relief. Assess spacing, stretch, orientation and relief on relevant faces. Correct unintended coordinate distortion without changing geometry or transforms. Replace the probe completely; preserve other material users and assignments. World-space field sizes are physical millimeters; object-space sizes follow carrier coordinates and transforms. Non-uniform dimensions and ancestor transforms affect mapping. Keep physical feature size consistent and all channels aligned; distinguish intended directional anatomy from smeared stretching.
8
+ Before construction, decompose the description/reference into broad variation, defining structure and fine grain. Record each component's relative scale, shape, direction, distribution, edge character and contrast, and its color/roughness/relief contributions. Apply text overrides while preserving remaining reference features. Distinguish reference illumination from intrinsic structure. Give microstructure independent scale and strength; superimpose components without dominant motifs obscuring it.
9
+ Build all planned components and finish channels together using field, shape and compose. Their supported operations and selectors are resolved internally; construct masks, palettes and blends directly from these helpers. For textured surfaces, apply the tested readability adjustment in the first construction: bump scale ×1.25, palette saturation ×1.15, palette lightness ×0.95 and scalar feature contrast ×1.15 around 0.5, clamped to 0..1. Apply color factors in the known display-color encoding, then decode back to linear RGB; native HSL inputs are additive offsets. Incorporate this once into chosen base controls, without an extra material/render pass or compounding it during refinement. Explicit flat, muted or pale requests take precedence; retain reference hues, defining features and the requested finish. These factors are a starting adjustment, not universal optimum values. Keep defining height and fine-noise height independently weighted; increasing all bump together can erase coherent highlights and change gloss into a matte finish. Relief responds to height slope: finer spacing, sharper edges and more noise octaves can strengthen normals at unchanged scale. Calibrated ranges describe particular fields, not quality ceilings or universal strengths.
10
+ Adjust palette saturation/lightness separately from feature coverage and edge contrast; use bounded scalar remaps before palettes or masked blends, checking clipped colors and changed coverage. Keep intrinsic color separate from illumination; preserve natural low-saturation features and coherent highlights. Author linear RGB; decode reference/display colors and preserve already-linear colors and scalar channels. Use roughness variation for finish breakup and signed height profiles for actual ridges/cavities; a dark spot alone does not establish a pit.
11
+ Inspect complementary overall, close and grazing views together under the existing scene lighting, geometry and render settings; adjust viewpoints for inspection. Match tonal contrast and reflectivity through highlight width, strength and breakup. Confirm every planned feature remains visibly resolved: a fine-noise node does not prove fine grain. Check that defining relief reads through lighting at the intended viewing distance, rather than only through its color mask. Subpixel features cannot remain individually resolved by increasing strength or octaves. If relief destroys the requested sheen, rebalance the offending feature's height/frequency before compensating roughness.
12
+ For each refinement, name the largest visible mismatch and the expected correction. Wrong topology/shape needs construction changes; wrong spacing, color, roughness or relief needs the corresponding parameter changes. Use known construction inputs and returned control handles to apply the correction in that batch. Read graphs when editing a previously unknown material or recovering unavailable/stale handles. Adjust related controls together and inspect once. Compare each candidate with the best inspected version at the same views; restore the better graph if a change weakens a defining feature. If tuning fails to improve a mismatch, change its construction rather than repeating strength changes. Continue while a specific, supported correction is likely to improve a substantial mismatch; stop when the result meets the request or further edits cease to help. Do not end with an uninspected edit. Print only relevant IDs, controls and observations; retain full graphs locally for edits.`;
@@ -0,0 +1,78 @@
1
+ import { z } from "zod";
2
+ import type { MaterialCatalog, MaterialField, MaterialFieldOptions, MaterialGraph, MaterialRecipeOptions } from "./types.js";
3
+ export declare const MATERIAL_RECIPE_RELIEF_GUIDANCE = "Pattern-specific calibration observations (native strength, object coordinates, detail 4, height and bump tested independently):\n- Dual-scale grid, featureSize 1: height 0.0003\u20130.001; start at 0.0003.\n- Noise, featureSize 0.04: 0.01\u20130.03 produced visible fine relief.\n- Crossed periodic threads, featureSize 0.04: 0.003\u20130.03 retained rounded thread shading.\n- Noise, featureSize 0.08: 0.01\u20130.03 produced low-amplitude grain.\n- Fine directional grain, featureSize {x:0.015,y:0.015,z:2}: 0.0003\u20130.003 produced faint directional relief; 0.01 and above produced stronger ridges.\nThese observations on curved and flat fixtures under fixed lighting are starting points, not quality targets or upper limits. Use independently weighted structural and micro height fields. Choose visible relief by its actual gradient, scale and requested finish; universal bump multipliers can destroy gloss. Compare close and grazing views without changing the scene. Height and bump strengths are not generally interchangeable.";
4
+ export declare const materialRecipeSchema: z.ZodObject<{
5
+ pattern: z.ZodEnum<{
6
+ noise: "noise";
7
+ grain: "grain";
8
+ woven: "woven";
9
+ grid: "grid";
10
+ }>;
11
+ coordinateSpace: z.ZodEnum<{
12
+ object: "object";
13
+ world: "world";
14
+ }>;
15
+ featureSize: z.ZodUnion<readonly [z.ZodNumber, z.ZodObject<{
16
+ x: z.ZodNumber;
17
+ y: z.ZodNumber;
18
+ z: z.ZodNumber;
19
+ }, z.core.$strict>]>;
20
+ colorA: z.ZodObject<{
21
+ x: z.ZodNumber;
22
+ y: z.ZodNumber;
23
+ z: z.ZodNumber;
24
+ }, z.core.$strict>;
25
+ colorB: z.ZodObject<{
26
+ x: z.ZodNumber;
27
+ y: z.ZodNumber;
28
+ z: z.ZodNumber;
29
+ }, z.core.$strict>;
30
+ roughnessA: z.ZodNumber;
31
+ roughnessB: z.ZodNumber;
32
+ metalness: z.ZodOptional<z.ZodNumber>;
33
+ detail: z.ZodOptional<z.ZodNumber>;
34
+ relief: z.ZodEnum<{
35
+ height: "height";
36
+ bump: "bump";
37
+ }>;
38
+ reliefScale: z.ZodNumber;
39
+ }, z.core.$strict>;
40
+ export declare const materialFieldSchema: z.ZodObject<{
41
+ coordinateSpace: z.ZodEnum<{
42
+ object: "object";
43
+ world: "world";
44
+ }>;
45
+ featureSize: z.ZodUnion<readonly [z.ZodNumber, z.ZodObject<{
46
+ x: z.ZodNumber;
47
+ y: z.ZodNumber;
48
+ z: z.ZodNumber;
49
+ }, z.core.$strict>]>;
50
+ detail: z.ZodOptional<z.ZodNumber>;
51
+ pattern: z.ZodEnum<{
52
+ noise: "noise";
53
+ grain: "grain";
54
+ woven: "woven";
55
+ grid: "grid";
56
+ cells: "cells";
57
+ }>;
58
+ rotation: z.ZodOptional<z.ZodObject<{
59
+ x: z.ZodNumber;
60
+ y: z.ZodNumber;
61
+ z: z.ZodNumber;
62
+ }, z.core.$strict>>;
63
+ cellMode: z.ZodOptional<z.ZodEnum<{
64
+ Color: "Color";
65
+ Distance: "Distance";
66
+ "Edge distance": "Edge distance";
67
+ "Smooth Distance": "Smooth Distance";
68
+ "Smooth Color": "Smooth Color";
69
+ }>>;
70
+ remap: z.ZodOptional<z.ZodObject<{
71
+ low: z.ZodNumber;
72
+ high: z.ZodNumber;
73
+ }, z.core.$strict>>;
74
+ }, z.core.$strict>;
75
+ export declare function materialFieldOperations(options: MaterialFieldOptions): string[];
76
+ export declare function materialRecipeOperations(options: MaterialRecipeOptions): string[];
77
+ export declare function makeMaterialField(options: MaterialFieldOptions, catalogue: MaterialCatalog): MaterialField;
78
+ export declare function makeMaterialRecipe(options: MaterialRecipeOptions, catalogue: MaterialCatalog): MaterialGraph;