@vizij/node-graph-authoring 0.0.4 → 0.1.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.
package/dist/index.cjs CHANGED
@@ -20,125 +20,52 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ EXPRESSION_FUNCTION_VOCABULARY: () => EXPRESSION_FUNCTION_VOCABULARY,
24
+ MACHINE_REPORT_VERSION: () => MACHINE_REPORT_VERSION,
23
25
  PRIMARY_SLOT_ALIAS: () => PRIMARY_SLOT_ALIAS,
24
26
  PRIMARY_SLOT_ID: () => PRIMARY_SLOT_ID,
27
+ RESERVED_EXPRESSION_VARIABLES: () => RESERVED_EXPRESSION_VARIABLES,
28
+ SCALAR_FUNCTIONS: () => SCALAR_FUNCTIONS,
29
+ SCALAR_FUNCTION_VOCABULARY: () => SCALAR_FUNCTION_VOCABULARY,
25
30
  addBindingSlot: () => addBindingSlot,
26
31
  bindingFromDefinition: () => bindingFromDefinition,
27
- bindingOperatorDefinitions: () => bindingOperatorDefinitions,
28
- bindingOperatorTypes: () => bindingOperatorTypes,
29
32
  bindingTargetFromComponent: () => bindingTargetFromComponent,
30
33
  bindingTargetFromInput: () => bindingTargetFromInput,
31
34
  bindingToDefinition: () => bindingToDefinition,
35
+ buildCanonicalBindingExpression: () => buildCanonicalBindingExpression,
36
+ buildMachineReport: () => buildMachineReport,
32
37
  buildRigGraphSpec: () => buildRigGraphSpec,
33
38
  collectExpressionReferences: () => collectExpressionReferences,
39
+ compileIrGraph: () => compileIrGraph,
34
40
  createDefaultBinding: () => createDefaultBinding,
35
41
  createDefaultBindings: () => createDefaultBindings,
36
42
  createDefaultInputValues: () => createDefaultInputValues,
37
- createDefaultOperatorSettings: () => createDefaultOperatorSettings,
38
43
  createDefaultParentBinding: () => createDefaultParentBinding,
39
- createDefaultRemap: () => createDefaultRemap,
44
+ createExpressionVariableTable: () => createExpressionVariableTable,
45
+ createIrGraphBuilder: () => createIrGraphBuilder,
46
+ createLegacyIrGraph: () => createLegacyIrGraph,
47
+ diffMachineReports: () => diffMachineReports,
40
48
  ensureBindingStructure: () => ensureBindingStructure,
41
- ensureOperatorParams: () => ensureOperatorParams,
42
- getBindingOperatorDefinition: () => getBindingOperatorDefinition,
43
49
  getPrimaryBindingSlot: () => getPrimaryBindingSlot,
44
50
  mapExpression: () => mapExpression,
45
51
  parseControlExpression: () => parseControlExpression,
46
52
  reconcileBindings: () => reconcileBindings,
47
- remapValue: () => remapValue,
48
53
  removeBindingSlot: () => removeBindingSlot,
49
- setBindingOperatorEnabled: () => setBindingOperatorEnabled,
54
+ toIrBindingSummary: () => toIrBindingSummary,
50
55
  updateBindingExpression: () => updateBindingExpression,
51
- updateBindingOperatorParam: () => updateBindingOperatorParam,
52
56
  updateBindingSlotAlias: () => updateBindingSlotAlias,
53
- updateBindingSlotRemap: () => updateBindingSlotRemap,
57
+ updateBindingSlotValueType: () => updateBindingSlotValueType,
54
58
  updateBindingWithInput: () => updateBindingWithInput
55
59
  });
56
60
  module.exports = __toCommonJS(index_exports);
57
61
 
58
62
  // src/graphBuilder.ts
59
- var import_utils2 = require("@vizij/utils");
60
- var import_utils3 = require("@vizij/utils");
63
+ var import_utils4 = require("@vizij/utils");
64
+ var import_utils5 = require("@vizij/utils");
65
+ var import_metadata2 = require("@vizij/node-graph-wasm/metadata");
61
66
 
62
67
  // src/state.ts
63
68
  var import_utils = require("@vizij/utils");
64
-
65
- // src/operators.ts
66
- var import_metadata = require("@vizij/node-graph-wasm/metadata");
67
- var OPERATOR_TYPES = [
68
- "spring",
69
- "damp",
70
- "slew"
71
- ];
72
- function valueJsonToNumber(value) {
73
- if (!value || typeof value !== "object") {
74
- return typeof value === "number" ? value : 0;
75
- }
76
- if ("float" in value && typeof value.float === "number") {
77
- return value.float;
78
- }
79
- if ("int" in value && typeof value.int === "number") {
80
- return value.int;
81
- }
82
- return 0;
83
- }
84
- var operatorDefinitionMap = /* @__PURE__ */ new Map();
85
- OPERATOR_TYPES.forEach((type) => {
86
- const signature = (0, import_metadata.requireNodeSignature)(type);
87
- const params = signature.params.map(
88
- (param) => ({
89
- id: param.id,
90
- label: param.label,
91
- doc: param.doc,
92
- min: param.min ?? void 0,
93
- max: param.max ?? void 0,
94
- defaultValue: valueJsonToNumber(param.default_json)
95
- })
96
- );
97
- operatorDefinitionMap.set(type, {
98
- type,
99
- nodeType: signature.type_id,
100
- label: signature.name,
101
- description: signature.doc,
102
- inputs: signature.inputs.map((input) => input.id),
103
- params
104
- });
105
- });
106
- function getBindingOperatorDefinition(type) {
107
- const definition = operatorDefinitionMap.get(type);
108
- if (!definition) {
109
- throw new Error(`Unknown binding operator type '${type}'.`);
110
- }
111
- return definition;
112
- }
113
- var bindingOperatorDefinitions = OPERATOR_TYPES.map((type) => getBindingOperatorDefinition(type));
114
- function createDefaultOperatorSettings(type) {
115
- const definition = getBindingOperatorDefinition(type);
116
- const params = {};
117
- definition.params.forEach((param) => {
118
- params[param.id] = param.defaultValue;
119
- });
120
- return {
121
- type,
122
- enabled: false,
123
- params
124
- };
125
- }
126
- function ensureOperatorParams(operator) {
127
- const definition = getBindingOperatorDefinition(operator.type);
128
- const params = {};
129
- definition.params.forEach((param) => {
130
- const value = operator.params?.[param.id];
131
- params[param.id] = typeof value === "number" ? value : param.defaultValue;
132
- });
133
- return {
134
- type: operator.type,
135
- enabled: !!operator.enabled,
136
- params
137
- };
138
- }
139
- var bindingOperatorTypes = OPERATOR_TYPES;
140
-
141
- // src/state.ts
142
69
  var VECTOR_ANIMATABLE_TYPES = /* @__PURE__ */ new Set(["vector2", "vector3", "euler", "rgb"]);
143
70
  function deriveComponentValueType(component) {
144
71
  if (component.component) {
@@ -177,90 +104,10 @@ function bindingTargetFromInput(input) {
177
104
  valueType: "scalar"
178
105
  };
179
106
  }
180
- var DEFAULT_INPUT_RANGE = { min: -1, max: 1 };
181
- var DEFAULT_INPUT_ANCHOR = 0;
182
- var EPSILON = 1e-6;
183
107
  var LEGACY_SLOT_PATTERN = /^slot_(\d+)$/i;
184
108
  var ALIAS_SANITIZE_PATTERN = /[^A-Za-z0-9_]+/g;
185
109
  var PRIMARY_SLOT_ID = "s1";
186
110
  var PRIMARY_SLOT_ALIAS = "s1";
187
- function createDefaultOperators() {
188
- return bindingOperatorTypes.map(
189
- (type) => createDefaultOperatorSettings(type)
190
- );
191
- }
192
- function normalizeOperator(operator) {
193
- const normalized = ensureOperatorParams(operator);
194
- const definition = getBindingOperatorDefinition(normalized.type);
195
- const params = {};
196
- definition.params.forEach((param) => {
197
- const value = normalized.params[param.id];
198
- params[param.id] = typeof value === "number" ? value : param.defaultValue;
199
- });
200
- return {
201
- type: normalized.type,
202
- enabled: !!normalized.enabled,
203
- params
204
- };
205
- }
206
- function ensureOperators(binding) {
207
- const existing = /* @__PURE__ */ new Map();
208
- (binding.operators ?? []).forEach((operator) => {
209
- try {
210
- existing.set(operator.type, normalizeOperator(operator));
211
- } catch {
212
- }
213
- });
214
- return bindingOperatorTypes.map((type) => {
215
- const current = existing.get(type);
216
- if (current) {
217
- return current;
218
- }
219
- return createDefaultOperatorSettings(type);
220
- });
221
- }
222
- function paramsEqual(a, b) {
223
- const aKeys = Object.keys(a);
224
- const bKeys = Object.keys(b);
225
- if (aKeys.length !== bKeys.length) {
226
- return false;
227
- }
228
- for (const key of aKeys) {
229
- if (a[key] !== b[key]) {
230
- return false;
231
- }
232
- }
233
- return true;
234
- }
235
- function operatorsEqual(a, b) {
236
- if (!a || a.length !== b.length) {
237
- return false;
238
- }
239
- for (let index = 0; index < a.length; index += 1) {
240
- const left = a[index];
241
- const right = b[index];
242
- if (left.type !== right.type || left.enabled !== right.enabled) {
243
- return false;
244
- }
245
- if (!paramsEqual(left.params, right.params)) {
246
- return false;
247
- }
248
- }
249
- return true;
250
- }
251
- function normalizeBindingWithOperators(binding) {
252
- const operators = ensureOperators(binding);
253
- if (operatorsEqual(binding.operators, operators)) {
254
- return { binding, operators };
255
- }
256
- return {
257
- binding: {
258
- ...binding,
259
- operators
260
- },
261
- operators
262
- };
263
- }
264
111
  function defaultSlotId(index) {
265
112
  return `s${index + 1}`;
266
113
  }
@@ -332,125 +179,11 @@ function rewriteLegacyExpression(expression, replacements) {
332
179
  return `s${digits}`;
333
180
  });
334
181
  }
335
- function clamp(value, min, max) {
336
- return Math.min(max, Math.max(min, value));
337
- }
338
- function isFiniteNumber(value) {
339
- return typeof value === "number" && Number.isFinite(value);
340
- }
341
- function deriveOutputDefaults(target) {
342
- const { min, max } = target.range;
343
- const anchor = clamp(target.defaultValue, min, max);
344
- return {
345
- outLow: min,
346
- outAnchor: anchor,
347
- outHigh: max
348
- };
349
- }
350
- function deriveInputDefaults() {
351
- return {
352
- inLow: DEFAULT_INPUT_RANGE.min,
353
- inAnchor: DEFAULT_INPUT_ANCHOR,
354
- inHigh: DEFAULT_INPUT_RANGE.max
355
- };
356
- }
357
- function migrateLegacyRemap(legacy, target) {
358
- const inputDefaults = deriveInputDefaults();
359
- const outputDefaults = deriveOutputDefaults(target);
360
- const defaults = {
361
- inLow: inputDefaults.inLow,
362
- inAnchor: inputDefaults.inAnchor,
363
- inHigh: inputDefaults.inHigh,
364
- outLow: outputDefaults.outLow,
365
- outAnchor: outputDefaults.outAnchor,
366
- outHigh: outputDefaults.outHigh
367
- };
368
- if ("inLow" in legacy && "inHigh" in legacy && "outLow" in legacy && "outHigh" in legacy) {
369
- const inLow2 = isFiniteNumber(legacy.inLow) ? legacy.inLow : defaults.inLow;
370
- const inAnchor2 = isFiniteNumber(legacy.inAnchor) ? legacy.inAnchor : defaults.inAnchor;
371
- const inHigh2 = isFiniteNumber(legacy.inHigh) ? legacy.inHigh : defaults.inHigh;
372
- let outLow = isFiniteNumber(legacy.outLow) ? legacy.outLow : defaults.outLow;
373
- let outHigh = isFiniteNumber(legacy.outHigh) ? legacy.outHigh : defaults.outHigh;
374
- if (outLow > outHigh) {
375
- const low = outHigh;
376
- const high = outLow;
377
- outLow = low;
378
- outHigh = high;
379
- }
380
- const outAnchor2 = clamp(
381
- isFiniteNumber(legacy.outAnchor) ? legacy.outAnchor : defaults.outAnchor,
382
- outLow,
383
- outHigh
384
- );
385
- return {
386
- inLow: inLow2,
387
- inAnchor: inAnchor2,
388
- inHigh: inHigh2,
389
- outLow,
390
- outAnchor: outAnchor2,
391
- outHigh
392
- };
393
- }
394
- const legacyTyped = legacy;
395
- const inLow = isFiniteNumber(legacyTyped.inMin) ? legacyTyped.inMin : defaults.inLow;
396
- const inHigh = isFiniteNumber(legacyTyped.inMax) ? legacyTyped.inMax : defaults.inHigh;
397
- const inAnchor = (inLow + inHigh) / 2;
398
- const legacyOutMid = isFiniteNumber(legacyTyped.outMin) && isFiniteNumber(legacyTyped.outMax) ? (legacyTyped.outMin + legacyTyped.outMax) / 2 : defaults.outAnchor;
399
- const outAnchor = clamp(legacyOutMid, defaults.outLow, defaults.outHigh);
400
- return {
401
- inLow,
402
- inAnchor,
403
- inHigh,
404
- outLow: defaults.outLow,
405
- outAnchor,
406
- outHigh: defaults.outHigh
407
- };
408
- }
409
- function normalizeRemap(remap, target) {
410
- if (!remap) {
411
- return createDefaultRemap(target);
412
- }
413
- return migrateLegacyRemap(remap, target);
414
- }
415
- function cloneRemap(remap) {
416
- return (0, import_utils.cloneRemapSettings)(remap);
417
- }
418
- function sanitizeRemap(remap, target) {
419
- const normalized = normalizeRemap(remap, target);
420
- const outputDefaults = deriveOutputDefaults(target);
421
- if (!Number.isFinite(normalized.outLow)) {
422
- normalized.outLow = outputDefaults.outLow;
423
- }
424
- if (!Number.isFinite(normalized.outHigh)) {
425
- normalized.outHigh = outputDefaults.outHigh;
426
- }
427
- if (!Number.isFinite(normalized.outAnchor)) {
428
- normalized.outAnchor = outputDefaults.outAnchor;
182
+ function cloneBindingMetadata(metadata) {
183
+ if (!metadata) {
184
+ return void 0;
429
185
  }
430
- if (normalized.outLow > normalized.outHigh) {
431
- const low = normalized.outHigh;
432
- const high = normalized.outLow;
433
- normalized.outLow = low;
434
- normalized.outHigh = high;
435
- }
436
- normalized.outAnchor = clamp(
437
- normalized.outAnchor,
438
- normalized.outLow,
439
- normalized.outHigh
440
- );
441
- return normalized;
442
- }
443
- function createDefaultRemap(target) {
444
- const inputDefaults = deriveInputDefaults();
445
- const outputDefaults = deriveOutputDefaults(target);
446
- return {
447
- inLow: inputDefaults.inLow,
448
- inAnchor: inputDefaults.inAnchor,
449
- inHigh: inputDefaults.inHigh,
450
- outLow: outputDefaults.outLow,
451
- outAnchor: outputDefaults.outAnchor,
452
- outHigh: outputDefaults.outHigh
453
- };
186
+ return (0, import_utils.cloneDeepSafe)(metadata);
454
187
  }
455
188
  function createDefaultBindings(components) {
456
189
  const bindings = {};
@@ -460,23 +193,20 @@ function createDefaultBindings(components) {
460
193
  return bindings;
461
194
  }
462
195
  function createDefaultBinding(component) {
463
- const remap = createDefaultRemap(component);
464
196
  const valueType = getTargetValueType(component);
197
+ const slots = [
198
+ {
199
+ id: PRIMARY_SLOT_ID,
200
+ alias: PRIMARY_SLOT_ALIAS,
201
+ inputId: null,
202
+ valueType
203
+ }
204
+ ];
465
205
  return {
466
206
  targetId: component.id,
467
207
  inputId: null,
468
- remap,
469
- slots: [
470
- {
471
- id: PRIMARY_SLOT_ID,
472
- alias: PRIMARY_SLOT_ALIAS,
473
- inputId: null,
474
- remap: cloneRemap(remap),
475
- valueType
476
- }
477
- ],
478
- expression: PRIMARY_SLOT_ALIAS,
479
- operators: createDefaultOperators()
208
+ slots,
209
+ expression: buildCanonicalExpressionFromSlots(slots)
480
210
  };
481
211
  }
482
212
  function createDefaultParentBinding(component) {
@@ -496,11 +226,10 @@ function createDefaultParentBinding(component) {
496
226
  ...ensured,
497
227
  inputId: import_utils.SELF_BINDING_ID,
498
228
  slots,
499
- expression: "self"
229
+ expression: buildCanonicalExpressionFromSlots(slots)
500
230
  };
501
231
  }
502
232
  function ensurePrimarySlot(binding, target) {
503
- const normalizedBindingRemap = sanitizeRemap(binding.remap, target);
504
233
  const targetValueType = getTargetValueType(target);
505
234
  const aliasReplacements = /* @__PURE__ */ new Map();
506
235
  const sourceSlots = Array.isArray(binding.slots) && binding.slots.length > 0 ? binding.slots : [
@@ -508,7 +237,6 @@ function ensurePrimarySlot(binding, target) {
508
237
  id: PRIMARY_SLOT_ID,
509
238
  alias: PRIMARY_SLOT_ALIAS,
510
239
  inputId: binding.inputId ?? null,
511
- remap: cloneRemap(normalizedBindingRemap),
512
240
  valueType: targetValueType
513
241
  }
514
242
  ];
@@ -523,8 +251,6 @@ function ensurePrimarySlot(binding, target) {
523
251
  if (replaced && replaced !== normalizedAlias) {
524
252
  aliasReplacements.set(replaced, normalizedAlias);
525
253
  }
526
- const slotRemapSource = slot.remap ?? (index === 0 ? normalizedBindingRemap : createDefaultRemap(target));
527
- const normalizedSlotRemap = sanitizeRemap(slotRemapSource, target);
528
254
  const inputId = slot.inputId !== void 0 && slot.inputId !== null ? slot.inputId : index === 0 ? binding.inputId ?? null : null;
529
255
  const slotValueType = sanitizeSlotValueType(
530
256
  slot.valueType,
@@ -534,13 +260,11 @@ function ensurePrimarySlot(binding, target) {
534
260
  id: normalizedId,
535
261
  alias: normalizedAlias,
536
262
  inputId,
537
- remap: cloneRemap(normalizedSlotRemap),
538
263
  valueType: slotValueType
539
264
  };
540
265
  }
541
266
  );
542
267
  const primary = normalizedSlots[0];
543
- const primaryRemap = sanitizeRemap(primary.remap, target);
544
268
  const primaryInputId = primary.inputId === import_utils.SELF_BINDING_ID ? import_utils.SELF_BINDING_ID : primary.inputId ?? binding.inputId ?? null;
545
269
  const primaryAlias = primaryInputId === import_utils.SELF_BINDING_ID ? "self" : primary.alias || PRIMARY_SLOT_ALIAS;
546
270
  normalizedSlots[0] = {
@@ -548,37 +272,31 @@ function ensurePrimarySlot(binding, target) {
548
272
  id: primary.id || PRIMARY_SLOT_ID,
549
273
  alias: primaryAlias,
550
274
  inputId: primaryInputId,
551
- remap: cloneRemap(primaryRemap),
552
275
  valueType: sanitizeSlotValueType(primary.valueType, targetValueType)
553
276
  };
554
277
  normalizedSlots.slice(1).forEach((slot, index) => {
555
- const slotRemap = sanitizeRemap(slot.remap, target);
556
278
  normalizedSlots[index + 1] = {
557
279
  ...slot,
558
280
  id: slot.id || defaultSlotId(index + 1),
559
281
  alias: slot.alias || defaultSlotId(index + 1),
560
- remap: cloneRemap(slotRemap),
561
282
  valueType: sanitizeSlotValueType(slot.valueType, targetValueType)
562
283
  };
563
284
  });
564
285
  const rawExpression = typeof binding.expression === "string" ? binding.expression.trim() : "";
565
- let expression = rawExpression.length > 0 ? rawExpression : normalizedSlots[0].alias;
566
- expression = rewriteLegacyExpression(expression, aliasReplacements);
286
+ const canonicalExpression = buildCanonicalExpressionFromSlots(normalizedSlots);
287
+ let expression;
288
+ if (expressionMatchesAliasOnly(rawExpression, normalizedSlots)) {
289
+ expression = canonicalExpression;
290
+ } else {
291
+ expression = rewriteLegacyExpression(rawExpression, aliasReplacements);
292
+ }
567
293
  const normalizedBinding = {
568
294
  ...binding,
569
295
  inputId: normalizedSlots[0].inputId ?? null,
570
- remap: cloneRemap(primaryRemap),
571
296
  slots: normalizedSlots,
572
297
  expression
573
298
  };
574
- const operators = ensureOperators(normalizedBinding);
575
- if (operatorsEqual(normalizedBinding.operators, operators)) {
576
- return normalizedBinding;
577
- }
578
- return {
579
- ...normalizedBinding,
580
- operators
581
- };
299
+ return normalizedBinding;
582
300
  }
583
301
  function createDefaultInputValues(inputs = []) {
584
302
  const values = {};
@@ -601,14 +319,13 @@ function addBindingSlot(binding, target) {
601
319
  const nextIndex = base.slots.length + 1;
602
320
  const slotId = defaultSlotId(nextIndex - 1);
603
321
  const alias = slotId;
604
- const remap = createDefaultRemap(target);
605
322
  const nextSlots = [
606
323
  ...base.slots,
607
324
  {
608
325
  id: slotId,
609
326
  alias,
610
327
  inputId: null,
611
- remap: cloneRemap(remap)
328
+ valueType: getTargetValueType(target)
612
329
  }
613
330
  ];
614
331
  return ensurePrimarySlot(
@@ -689,206 +406,105 @@ function updateBindingSlotAlias(binding, target, slotId, nextAlias) {
689
406
  );
690
407
  return updated;
691
408
  }
692
- function setBindingOperatorEnabled(binding, type, enabled) {
693
- const { binding: normalizedBinding, operators } = normalizeBindingWithOperators(binding);
694
- let changed = false;
695
- const nextOperators = operators.map((operator) => {
696
- if (operator.type !== type) {
697
- return operator;
698
- }
699
- if (operator.enabled === enabled) {
700
- return operator;
701
- }
702
- changed = true;
703
- return {
704
- ...operator,
705
- enabled
706
- };
707
- });
708
- if (!changed) {
709
- return normalizedBinding;
710
- }
711
- if (operatorsEqual(normalizedBinding.operators, nextOperators)) {
712
- return normalizedBinding;
409
+ function updateBindingSlotValueType(binding, target, slotId, nextValueType) {
410
+ const base = ensurePrimarySlot(binding, target);
411
+ const slotIndex = base.slots.findIndex((slot) => slot.id === slotId);
412
+ if (slotIndex < 0) {
413
+ return base;
713
414
  }
714
- return {
715
- ...normalizedBinding,
716
- operators: nextOperators
717
- };
718
- }
719
- function updateBindingOperatorParam(binding, type, paramId, value) {
720
- const { binding: normalizedBinding, operators } = normalizeBindingWithOperators(binding);
721
- const definition = getBindingOperatorDefinition(type);
722
- const paramDefinition = definition.params.find(
723
- (param) => param.id === paramId
415
+ const normalizedType = sanitizeSlotValueType(
416
+ nextValueType,
417
+ getTargetValueType(target)
724
418
  );
725
- if (!paramDefinition) {
726
- return normalizedBinding;
727
- }
728
- let nextValue = value;
729
- if (typeof paramDefinition.min === "number") {
730
- nextValue = Math.max(paramDefinition.min, nextValue);
731
- }
732
- if (typeof paramDefinition.max === "number") {
733
- nextValue = Math.min(paramDefinition.max, nextValue);
734
- }
735
- let changed = false;
736
- const nextOperators = operators.map((operator) => {
737
- if (operator.type !== type) {
738
- return operator;
739
- }
740
- if (operator.params[paramId] === nextValue) {
741
- return operator;
419
+ const slots = base.slots.map((slot, index) => {
420
+ if (index !== slotIndex) {
421
+ return slot;
742
422
  }
743
- changed = true;
744
423
  return {
745
- ...operator,
746
- params: {
747
- ...operator.params,
748
- [paramId]: nextValue
749
- }
424
+ ...slot,
425
+ valueType: normalizedType
750
426
  };
751
427
  });
752
- if (!changed) {
753
- return normalizedBinding;
754
- }
755
- if (operatorsEqual(normalizedBinding.operators, nextOperators)) {
756
- return normalizedBinding;
757
- }
758
428
  return {
759
- ...normalizedBinding,
760
- operators: nextOperators
429
+ ...base,
430
+ slots
761
431
  };
762
432
  }
763
433
  function updateBindingExpression(binding, target, expression) {
764
434
  const base = ensurePrimarySlot(binding, target);
765
435
  const trimmed = expression.trim();
436
+ const canonicalExpression = buildCanonicalExpressionFromSlots(base.slots);
766
437
  return {
767
438
  ...base,
768
- expression: trimmed.length > 0 ? trimmed : base.slots[0]?.alias ?? PRIMARY_SLOT_ALIAS
439
+ expression: trimmed.length > 0 ? trimmed : canonicalExpression
769
440
  };
770
441
  }
771
- function updateBindingSlotRemap(binding, target, slotId, field, value) {
772
- const base = ensurePrimarySlot(binding, target);
773
- const nextSlots = base.slots.map((slot) => {
774
- if (slot.id !== slotId) {
775
- return slot;
776
- }
777
- const updatedRemap = {
778
- ...slot.remap,
779
- [field]: value
780
- };
781
- const sanitized = sanitizeRemap(updatedRemap, target);
782
- return {
783
- ...slot,
784
- remap: cloneRemap(sanitized)
785
- };
786
- });
787
- const updated = ensurePrimarySlot(
788
- {
789
- ...base,
790
- slots: nextSlots
791
- },
792
- target
793
- );
794
- if (updated.slots[0]?.id === slotId) {
795
- updated.remap = {
796
- ...updated.remap,
797
- [field]: value
798
- };
799
- }
800
- return updated;
801
- }
802
442
  function updateBindingWithInput(binding, target, input, slotId = PRIMARY_SLOT_ID) {
803
443
  const base = ensurePrimarySlot(binding, target);
444
+ const existingExpression = typeof base.expression === "string" ? base.expression.trim() : "";
445
+ const canonicalBefore = buildCanonicalExpressionFromSlots(base.slots);
446
+ const expressionWasDefault = expressionMatchesAliasOnly(existingExpression, base.slots) || expressionsEquivalent(existingExpression, canonicalBefore);
804
447
  const slotIndex = base.slots.findIndex((slot) => slot.id === slotId);
805
448
  const effectiveIndex = slotIndex >= 0 ? slotIndex : base.slots.length;
806
- const slots = base.slots.map((slot) => ({
807
- ...slot,
808
- remap: cloneRemap(slot.remap)
809
- }));
449
+ const slots = base.slots.map((slot) => ({ ...slot }));
810
450
  if (slotIndex === -1) {
811
451
  const alias = slotId === PRIMARY_SLOT_ID && slots.length === 0 ? PRIMARY_SLOT_ALIAS : slotId;
812
452
  slots.push({
813
453
  id: slotId,
814
454
  alias,
815
455
  inputId: null,
816
- remap: cloneRemap(createDefaultRemap(target))
456
+ valueType: getTargetValueType(target)
817
457
  });
818
458
  }
819
459
  const currentSlot = slots[effectiveIndex];
460
+ let nextBinding;
820
461
  if (!input) {
821
- const normalizedSlotRemap = sanitizeRemap(currentSlot.remap, target);
822
- const updatedRemap2 = {
823
- ...normalizedSlotRemap,
824
- inLow: DEFAULT_INPUT_RANGE.min,
825
- inAnchor: DEFAULT_INPUT_ANCHOR,
826
- inHigh: DEFAULT_INPUT_RANGE.max
827
- };
828
462
  slots[effectiveIndex] = {
829
463
  ...currentSlot,
830
- inputId: null,
831
- remap: cloneRemap(updatedRemap2)
464
+ inputId: null
832
465
  };
833
466
  if (effectiveIndex === 0) {
834
- return {
467
+ nextBinding = {
835
468
  ...base,
836
469
  inputId: null,
837
- remap: cloneRemap(updatedRemap2),
470
+ slots
471
+ };
472
+ } else {
473
+ nextBinding = {
474
+ ...base,
838
475
  slots
839
476
  };
840
477
  }
841
- return {
842
- ...base,
843
- slots
478
+ } else {
479
+ slots[effectiveIndex] = {
480
+ ...currentSlot,
481
+ inputId: input.id
844
482
  };
483
+ if (effectiveIndex === 0) {
484
+ nextBinding = {
485
+ ...base,
486
+ inputId: input.id,
487
+ slots
488
+ };
489
+ } else {
490
+ nextBinding = {
491
+ ...base,
492
+ slots
493
+ };
494
+ }
845
495
  }
846
- const normalizedRemap = sanitizeRemap(currentSlot.remap, target);
847
- const updatedRemap = {
848
- ...normalizedRemap,
849
- inLow: input.range.min,
850
- inAnchor: clamp(input.defaultValue, input.range.min, input.range.max),
851
- inHigh: input.range.max,
852
- ...deriveOutputDefaults(target)
853
- };
854
- slots[effectiveIndex] = {
855
- ...currentSlot,
856
- inputId: input.id,
857
- remap: cloneRemap(updatedRemap)
858
- };
859
- if (effectiveIndex === 0) {
860
- return {
861
- ...base,
862
- inputId: input.id,
863
- remap: cloneRemap(updatedRemap),
864
- slots
865
- };
496
+ if (!expressionWasDefault) {
497
+ return nextBinding;
498
+ }
499
+ const canonicalAfter = buildCanonicalExpressionFromSlots(nextBinding.slots);
500
+ if (expressionsEquivalent(nextBinding.expression ?? "", canonicalAfter)) {
501
+ return nextBinding;
866
502
  }
867
503
  return {
868
- ...base,
869
- slots
504
+ ...nextBinding,
505
+ expression: canonicalAfter
870
506
  };
871
507
  }
872
- function remapValue(value, remap) {
873
- const { inLow, inAnchor, inHigh, outLow, outAnchor, outHigh } = remap;
874
- if (Number.isNaN(value)) {
875
- return outAnchor;
876
- }
877
- if (value <= inAnchor) {
878
- const span2 = inAnchor - inLow;
879
- if (Math.abs(span2) < EPSILON) {
880
- return outLow;
881
- }
882
- const t2 = (value - inLow) / span2;
883
- return outLow + t2 * (outAnchor - outLow);
884
- }
885
- const span = inHigh - inAnchor;
886
- if (Math.abs(span) < EPSILON) {
887
- return outHigh;
888
- }
889
- const t = (value - inAnchor) / span;
890
- return outAnchor + t * (outHigh - outAnchor);
891
- }
892
508
  function reconcileBindings(previous, components) {
893
509
  const next = {};
894
510
  components.forEach((component) => {
@@ -906,12 +522,10 @@ function reconcileBindings(previous, components) {
906
522
  if (replaced && replaced !== normalizedAlias) {
907
523
  aliasReplacements.set(replaced, normalizedAlias);
908
524
  }
909
- const slotRemap = sanitizeRemap(slot.remap, component);
910
525
  return {
911
526
  ...slot,
912
527
  id: normalizedId,
913
- alias: normalizedAlias,
914
- remap: cloneRemap(slotRemap)
528
+ alias: normalizedAlias
915
529
  };
916
530
  });
917
531
  const primary = slots[0];
@@ -921,7 +535,6 @@ function reconcileBindings(previous, components) {
921
535
  ...ensured,
922
536
  targetId: component.id,
923
537
  inputId: primary.inputId ?? null,
924
- remap: cloneRemap(primary.remap),
925
538
  slots,
926
539
  expression
927
540
  };
@@ -932,20 +545,11 @@ function reconcileBindings(previous, components) {
932
545
  return next;
933
546
  }
934
547
  function bindingToDefinition(binding) {
935
- const operators = binding.operators ? binding.operators.map((operator) => ({
936
- type: operator.type,
937
- enabled: !!operator.enabled,
938
- params: { ...operator.params }
939
- })) : void 0;
940
548
  const definition = {
941
549
  inputId: binding.inputId ?? null,
942
- remap: cloneRemap(binding.remap),
943
- slots: binding.slots.map((slot) => ({
944
- ...slot,
945
- remap: cloneRemap(slot.remap)
946
- })),
550
+ slots: binding.slots.map((slot) => ({ ...slot })),
947
551
  expression: binding.expression,
948
- operators
552
+ metadata: cloneBindingMetadata(binding.metadata)
949
553
  };
950
554
  return definition;
951
555
  }
@@ -953,27 +557,57 @@ function bindingFromDefinition(target, definition) {
953
557
  if (!definition) {
954
558
  return createDefaultBinding(target);
955
559
  }
956
- const definitionOperators = definition.operators;
957
560
  const binding = {
958
561
  targetId: target.id,
959
562
  inputId: definition.inputId ?? null,
960
- remap: cloneRemap(definition.remap),
961
- slots: definition.slots.map((slot) => ({
962
- ...slot,
963
- remap: cloneRemap(slot.remap)
964
- })),
563
+ slots: definition.slots.map((slot) => ({ ...slot })),
965
564
  expression: definition.expression,
966
- operators: definitionOperators ? definitionOperators.map((operator) => ({
967
- type: operator.type,
968
- enabled: !!operator.enabled,
969
- params: { ...operator.params }
970
- })) : void 0
565
+ metadata: cloneBindingMetadata(definition.metadata)
971
566
  };
972
567
  return ensureBindingStructure(binding, target);
973
568
  }
569
+ function normalizeSlotAliasForExpression(slot, index) {
570
+ if (slot.alias && slot.alias.trim().length > 0) {
571
+ return slot.alias.trim();
572
+ }
573
+ if (slot.id && slot.id.trim().length > 0) {
574
+ return slot.id.trim();
575
+ }
576
+ return defaultSlotId(index);
577
+ }
578
+ function buildAliasOnlyExpression(slots) {
579
+ if (!slots.length) {
580
+ return PRIMARY_SLOT_ALIAS;
581
+ }
582
+ return slots.map((slot, index) => normalizeSlotAliasForExpression(slot, index)).join(" + ");
583
+ }
584
+ function buildCanonicalExpressionFromSlots(slots) {
585
+ return buildAliasOnlyExpression(slots);
586
+ }
587
+ function expressionsEquivalent(left, right) {
588
+ return left.trim() === right.trim();
589
+ }
590
+ function expressionMatchesAliasOnly(expression, slots) {
591
+ if (expression.trim().length === 0) {
592
+ return true;
593
+ }
594
+ const aliasOnly = buildAliasOnlyExpression(slots);
595
+ return expressionsEquivalent(expression, aliasOnly);
596
+ }
597
+ function buildCanonicalBindingExpression(binding) {
598
+ const slots = binding.slots ?? [];
599
+ return buildCanonicalExpressionFromSlots(slots);
600
+ }
974
601
 
975
602
  // src/expression.ts
976
- var WHITESPACE = /\s/;
603
+ var STANDARD_WHITESPACE = /\s/;
604
+ var EXTRA_WHITESPACE_CHARS = /* @__PURE__ */ new Set(["\0", "\u200B", "\uFEFF"]);
605
+ function isWhitespaceCharacter(char) {
606
+ if (!char) {
607
+ return false;
608
+ }
609
+ return STANDARD_WHITESPACE.test(char) || EXTRA_WHITESPACE_CHARS.has(char);
610
+ }
977
611
  var IDENT_START = /[A-Za-z_]/;
978
612
  var IDENT_PART = /[A-Za-z0-9_]/;
979
613
  var DIGIT = /[0-9]/;
@@ -1211,6 +845,10 @@ var ControlExpressionParser = class {
1211
845
  });
1212
846
  return null;
1213
847
  }
848
+ if (char === "[") {
849
+ this.index += 1;
850
+ return this.parseVectorLiteral("]", "vector literal");
851
+ }
1214
852
  if (IDENT_START.test(char)) {
1215
853
  return this.parseIdentifierOrFunction();
1216
854
  }
@@ -1239,6 +877,9 @@ var ControlExpressionParser = class {
1239
877
  this.skipWhitespace();
1240
878
  if (this.peek() === "(") {
1241
879
  this.index += 1;
880
+ if (name.toLowerCase() === "vec") {
881
+ return this.parseVectorLiteral(")", `"${name}" literal`);
882
+ }
1242
883
  const args = [];
1243
884
  this.skipWhitespace();
1244
885
  if (this.peek() === ")") {
@@ -1301,28 +942,134 @@ var ControlExpressionParser = class {
1301
942
  name
1302
943
  };
1303
944
  }
1304
- parseNumber() {
945
+ parseVectorLiteral(terminator, context) {
946
+ const values = this.parseVectorLiteralValues(terminator, context);
947
+ if (!values) {
948
+ return null;
949
+ }
950
+ return {
951
+ type: "VectorLiteral",
952
+ values
953
+ };
954
+ }
955
+ parseVectorLiteralValues(terminator, context) {
956
+ const values = [];
957
+ this.skipWhitespace();
958
+ if (this.peek() === terminator) {
959
+ this.errors.push({
960
+ index: this.index,
961
+ message: `Expected at least one value in ${context}.`
962
+ });
963
+ return null;
964
+ }
965
+ while (true) {
966
+ this.skipWhitespace();
967
+ const literalValue = this.parseNumericLiteralValue();
968
+ if (literalValue === null) {
969
+ this.errors.push({
970
+ index: this.index,
971
+ message: `Expected numeric literal in ${context}.`
972
+ });
973
+ return null;
974
+ }
975
+ values.push(literalValue);
976
+ this.skipWhitespace();
977
+ const next = this.peek();
978
+ if (next === ",") {
979
+ this.index += 1;
980
+ this.skipWhitespace();
981
+ if (this.peek() === terminator) {
982
+ this.errors.push({
983
+ index: this.index,
984
+ message: `Expected value after "," in ${context}.`
985
+ });
986
+ return null;
987
+ }
988
+ continue;
989
+ }
990
+ if (next === terminator) {
991
+ this.index += 1;
992
+ break;
993
+ }
994
+ if (next === null) {
995
+ this.errors.push({
996
+ index: this.index,
997
+ message: `Unterminated ${context}.`
998
+ });
999
+ } else {
1000
+ this.errors.push({
1001
+ index: this.index,
1002
+ message: `Expected "," or "${terminator}" in ${context}.`
1003
+ });
1004
+ }
1005
+ return null;
1006
+ }
1007
+ if (values.length === 0) {
1008
+ this.errors.push({
1009
+ index: this.index,
1010
+ message: `Expected at least one value in ${context}.`
1011
+ });
1012
+ return null;
1013
+ }
1014
+ return values;
1015
+ }
1016
+ parseNumericLiteralValue(allowLeadingSign = true) {
1305
1017
  const start = this.index;
1018
+ if (allowLeadingSign && (this.peek() === "+" || this.peek() === "-")) {
1019
+ this.index += 1;
1020
+ }
1306
1021
  let hasDigits = false;
1022
+ let seenDecimal = false;
1023
+ let seenExponent = false;
1024
+ let exponentDigits = false;
1025
+ let inExponent = false;
1307
1026
  while (!this.isAtEnd()) {
1308
1027
  const char = this.peek();
1309
1028
  if (DIGIT.test(char)) {
1310
1029
  hasDigits = true;
1030
+ if (inExponent) {
1031
+ exponentDigits = true;
1032
+ }
1311
1033
  this.index += 1;
1312
1034
  continue;
1313
1035
  }
1314
- if (char === ".") {
1036
+ if (char === "." && !seenDecimal && !seenExponent) {
1037
+ seenDecimal = true;
1315
1038
  this.index += 1;
1316
1039
  continue;
1317
1040
  }
1318
- break;
1319
- }
1320
- const raw = this.input.slice(start, this.index);
1321
- const value = Number(raw);
1322
- if (!hasDigits || Number.isNaN(value)) {
1041
+ if ((char === "e" || char === "E") && !seenExponent && hasDigits) {
1042
+ seenExponent = true;
1043
+ inExponent = true;
1044
+ exponentDigits = false;
1045
+ this.index += 1;
1046
+ const sign = this.peek();
1047
+ if (sign === "+" || sign === "-") {
1048
+ this.index += 1;
1049
+ }
1050
+ continue;
1051
+ }
1052
+ break;
1053
+ }
1054
+ if (!hasDigits || seenExponent && !exponentDigits) {
1055
+ this.index = start;
1056
+ return null;
1057
+ }
1058
+ const raw = this.input.slice(start, this.index);
1059
+ const value = Number(raw);
1060
+ if (!Number.isFinite(value)) {
1061
+ this.index = start;
1062
+ return null;
1063
+ }
1064
+ return value;
1065
+ }
1066
+ parseNumber() {
1067
+ const start = this.index;
1068
+ const value = this.parseNumericLiteralValue(false);
1069
+ if (value === null) {
1323
1070
  this.errors.push({
1324
1071
  index: start,
1325
- message: `Invalid numeric literal "${raw}".`
1072
+ message: "Invalid numeric literal."
1326
1073
  });
1327
1074
  return null;
1328
1075
  }
@@ -1341,130 +1088,1039 @@ var ControlExpressionParser = class {
1341
1088
  return null;
1342
1089
  }
1343
1090
  skipWhitespace() {
1344
- while (!this.isAtEnd() && WHITESPACE.test(this.peek())) {
1091
+ while (!this.isAtEnd()) {
1092
+ const char = this.peek();
1093
+ if (!isWhitespaceCharacter(char)) {
1094
+ break;
1095
+ }
1345
1096
  this.index += 1;
1346
1097
  }
1347
1098
  }
1348
- peek() {
1349
- if (this.index >= this.input.length) {
1350
- return null;
1351
- }
1352
- return this.input[this.index] ?? null;
1099
+ peek() {
1100
+ if (this.index >= this.input.length) {
1101
+ return null;
1102
+ }
1103
+ return this.input[this.index] ?? null;
1104
+ }
1105
+ isAtEnd() {
1106
+ return this.index >= this.input.length;
1107
+ }
1108
+ };
1109
+ function parseControlExpression(expression) {
1110
+ const parser = new ControlExpressionParser(expression);
1111
+ return parser.parse();
1112
+ }
1113
+ function collectExpressionReferences(node, target = /* @__PURE__ */ new Set()) {
1114
+ if (!node) {
1115
+ return target;
1116
+ }
1117
+ switch (node.type) {
1118
+ case "Reference":
1119
+ target.add(node.name);
1120
+ break;
1121
+ case "Unary":
1122
+ collectExpressionReferences(node.operand, target);
1123
+ break;
1124
+ case "Binary":
1125
+ collectExpressionReferences(node.left, target);
1126
+ collectExpressionReferences(node.right, target);
1127
+ break;
1128
+ case "Function":
1129
+ node.args.forEach((arg) => collectExpressionReferences(arg, target));
1130
+ break;
1131
+ default:
1132
+ break;
1133
+ }
1134
+ return target;
1135
+ }
1136
+ function mapExpression(node, visit) {
1137
+ visit(node);
1138
+ switch (node.type) {
1139
+ case "Unary":
1140
+ mapExpression(node.operand, visit);
1141
+ break;
1142
+ case "Binary":
1143
+ mapExpression(node.left, visit);
1144
+ mapExpression(node.right, visit);
1145
+ break;
1146
+ case "Function":
1147
+ node.args.forEach((arg) => mapExpression(arg, visit));
1148
+ break;
1149
+ default:
1150
+ break;
1151
+ }
1152
+ }
1153
+
1154
+ // src/expressionFunctions.ts
1155
+ var import_metadata = require("@vizij/node-graph-wasm/metadata");
1156
+ var FUNCTION_OVERRIDES = {
1157
+ case: {
1158
+ replaceInputs: [
1159
+ { id: "selector", optional: false, valueType: "any" },
1160
+ { id: "default", optional: false, valueType: "any" }
1161
+ ],
1162
+ variadic: { id: "operand", min: 1, max: null, valueType: "any" },
1163
+ minArgs: 3,
1164
+ maxArgs: null,
1165
+ params: []
1166
+ },
1167
+ slew: {
1168
+ dropTrailingInputs: 1,
1169
+ params: [
1170
+ {
1171
+ id: "max_rate",
1172
+ label: "max_rate",
1173
+ optional: false,
1174
+ valueType: "scalar"
1175
+ }
1176
+ ],
1177
+ minArgs: 2,
1178
+ maxArgs: 2
1179
+ }
1180
+ };
1181
+ var FUNCTION_CONFIGS = [
1182
+ {
1183
+ typeId: "sin",
1184
+ names: ["sin"],
1185
+ category: "math",
1186
+ description: "Sine of the input value (radians)."
1187
+ },
1188
+ {
1189
+ typeId: "cos",
1190
+ names: ["cos"],
1191
+ category: "math",
1192
+ description: "Cosine of the input value (radians)."
1193
+ },
1194
+ {
1195
+ typeId: "tan",
1196
+ names: ["tan"],
1197
+ category: "math",
1198
+ description: "Tangent of the input value (radians)."
1199
+ },
1200
+ {
1201
+ typeId: "power",
1202
+ names: ["power", "pow"],
1203
+ category: "math",
1204
+ description: "Raise a base value to an exponent."
1205
+ },
1206
+ {
1207
+ typeId: "log",
1208
+ names: ["log"],
1209
+ category: "math",
1210
+ description: "Logarithm of the input value."
1211
+ },
1212
+ {
1213
+ typeId: "clamp",
1214
+ names: ["clamp"],
1215
+ category: "utility",
1216
+ description: "Clamp an input between min/max."
1217
+ },
1218
+ {
1219
+ typeId: "remap",
1220
+ names: ["remap"],
1221
+ category: "utility",
1222
+ description: "Linearly map a value from the input range to the output range."
1223
+ },
1224
+ {
1225
+ typeId: "centered_remap",
1226
+ names: ["centeredremap", "centered_remap"],
1227
+ category: "utility",
1228
+ description: "Remap a value using in/out ranges that include explicit anchors."
1229
+ },
1230
+ {
1231
+ typeId: "piecewise_remap",
1232
+ names: ["piecewiseremap", "piecewise_remap"],
1233
+ category: "utility",
1234
+ description: "Map a value through piecewise-linear breakpoints."
1235
+ },
1236
+ {
1237
+ typeId: "abs",
1238
+ names: ["abs"],
1239
+ category: "math",
1240
+ description: "Absolute value of the input."
1241
+ },
1242
+ {
1243
+ typeId: "add",
1244
+ names: ["add"],
1245
+ category: "math",
1246
+ description: "Sum all operands together."
1247
+ },
1248
+ {
1249
+ typeId: "multiply",
1250
+ names: ["multiply"],
1251
+ category: "math",
1252
+ description: "Multiply operands together."
1253
+ },
1254
+ {
1255
+ typeId: "subtract",
1256
+ names: ["subtract"],
1257
+ category: "math",
1258
+ description: "Subtract rhs from lhs."
1259
+ },
1260
+ {
1261
+ typeId: "divide",
1262
+ names: ["divide"],
1263
+ category: "math",
1264
+ description: "Divide lhs by rhs."
1265
+ },
1266
+ {
1267
+ typeId: "min",
1268
+ names: ["min"],
1269
+ category: "math",
1270
+ description: "Minimum of all operands."
1271
+ },
1272
+ {
1273
+ typeId: "max",
1274
+ names: ["max"],
1275
+ category: "math",
1276
+ description: "Maximum of all operands."
1277
+ },
1278
+ {
1279
+ typeId: "modulo",
1280
+ names: ["modulo", "mod"],
1281
+ category: "math",
1282
+ description: "Modulo of lhs by rhs."
1283
+ },
1284
+ {
1285
+ typeId: "greaterthan",
1286
+ names: ["greaterthan", "gt"],
1287
+ minArgs: 2,
1288
+ maxArgs: 2,
1289
+ category: "logic",
1290
+ description: "Return true when lhs > rhs."
1291
+ },
1292
+ {
1293
+ typeId: "lessthan",
1294
+ names: ["lessthan", "lt"],
1295
+ minArgs: 2,
1296
+ maxArgs: 2,
1297
+ category: "logic",
1298
+ description: "Return true when lhs < rhs."
1299
+ },
1300
+ {
1301
+ typeId: "equal",
1302
+ names: ["equal", "eq"],
1303
+ minArgs: 2,
1304
+ maxArgs: 2,
1305
+ category: "logic",
1306
+ description: "Return true when operands are equal."
1307
+ },
1308
+ {
1309
+ typeId: "notequal",
1310
+ names: ["notequal", "neq", "ne"],
1311
+ minArgs: 2,
1312
+ maxArgs: 2,
1313
+ category: "logic",
1314
+ description: "Return true when operands differ."
1315
+ },
1316
+ {
1317
+ typeId: "and",
1318
+ names: ["and"],
1319
+ minArgs: 2,
1320
+ maxArgs: 2,
1321
+ category: "logic",
1322
+ description: "Logical AND."
1323
+ },
1324
+ {
1325
+ typeId: "or",
1326
+ names: ["or"],
1327
+ minArgs: 2,
1328
+ maxArgs: 2,
1329
+ category: "logic",
1330
+ description: "Logical OR."
1331
+ },
1332
+ {
1333
+ typeId: "xor",
1334
+ names: ["xor"],
1335
+ minArgs: 2,
1336
+ maxArgs: 2,
1337
+ category: "logic",
1338
+ description: "Logical XOR."
1339
+ },
1340
+ {
1341
+ typeId: "not",
1342
+ names: ["not"],
1343
+ minArgs: 1,
1344
+ maxArgs: 1,
1345
+ category: "logic",
1346
+ description: "Logical NOT of input."
1347
+ },
1348
+ {
1349
+ typeId: "if",
1350
+ names: ["if"],
1351
+ minArgs: 2,
1352
+ maxArgs: 3,
1353
+ category: "logic",
1354
+ description: "Conditional branch with optional fallback."
1355
+ },
1356
+ {
1357
+ typeId: "round",
1358
+ names: ["round"],
1359
+ category: "math",
1360
+ description: "Round input using the configured mode (floor/ceil/trunc)."
1361
+ },
1362
+ {
1363
+ typeId: "case",
1364
+ names: ["case"],
1365
+ minArgs: 3,
1366
+ category: "logic",
1367
+ description: "Route the selector value to matching labeled branches."
1368
+ },
1369
+ {
1370
+ typeId: "time",
1371
+ names: ["time"],
1372
+ minArgs: 0,
1373
+ maxArgs: 0,
1374
+ category: "time",
1375
+ description: "Graph time in seconds."
1376
+ },
1377
+ {
1378
+ typeId: "oscillator",
1379
+ names: ["oscillator"],
1380
+ minArgs: 2,
1381
+ maxArgs: 2,
1382
+ category: "time",
1383
+ description: "Sine/cosine oscillator driven by time."
1384
+ },
1385
+ {
1386
+ typeId: "spring",
1387
+ names: ["spring"],
1388
+ category: "time",
1389
+ description: "Spring toward the target with configurable stiffness and damping."
1390
+ },
1391
+ {
1392
+ typeId: "damp",
1393
+ names: ["damp"],
1394
+ category: "time",
1395
+ description: "Damp input values toward zero at the specified rate."
1396
+ },
1397
+ {
1398
+ typeId: "slew",
1399
+ names: ["slew"],
1400
+ category: "time",
1401
+ description: "Limit the rate of change between inputs over time."
1402
+ },
1403
+ {
1404
+ typeId: "default-blend",
1405
+ names: ["defaultblend", "blend"],
1406
+ category: "utility",
1407
+ description: "Blend operand values using optional baseline/offset and weight inputs."
1408
+ },
1409
+ {
1410
+ typeId: "blendweightedaverage",
1411
+ names: ["blendweightedaverage"],
1412
+ category: "utility",
1413
+ description: "Compute weighted average blends using aggregate sums."
1414
+ },
1415
+ {
1416
+ typeId: "blendadditive",
1417
+ names: ["blendadditive"],
1418
+ category: "utility",
1419
+ description: "Additive blending helper."
1420
+ },
1421
+ {
1422
+ typeId: "blendmultiply",
1423
+ names: ["blendmultiply"],
1424
+ category: "utility",
1425
+ description: "Multiplicative blending helper."
1426
+ },
1427
+ {
1428
+ typeId: "blendweightedoverlay",
1429
+ names: ["blendweightedoverlay"],
1430
+ category: "utility",
1431
+ description: "Overlay blending helper driven by weighted sums."
1432
+ },
1433
+ {
1434
+ typeId: "blendweightedaverageoverlay",
1435
+ names: ["blendweightedaverageoverlay"],
1436
+ category: "utility",
1437
+ description: "Overlay helper that adds averaged deltas to the base value."
1438
+ },
1439
+ {
1440
+ typeId: "blendmax",
1441
+ names: ["blendmax"],
1442
+ category: "utility",
1443
+ description: "Select the operand whose effective weight is largest."
1444
+ },
1445
+ {
1446
+ typeId: "vec3cross",
1447
+ names: ["vec3cross", "cross"],
1448
+ category: "vector",
1449
+ description: "Cross product of vectors A \xD7 B."
1450
+ },
1451
+ {
1452
+ typeId: "vectoradd",
1453
+ names: ["vectoradd", "vadd"],
1454
+ category: "vector",
1455
+ description: "Element-wise sum of vectors."
1456
+ },
1457
+ {
1458
+ typeId: "vectorsubtract",
1459
+ names: ["vectorsubtract", "vsub"],
1460
+ category: "vector",
1461
+ description: "Element-wise subtraction of vectors."
1462
+ },
1463
+ {
1464
+ typeId: "vectormultiply",
1465
+ names: ["vectormultiply", "vmul"],
1466
+ category: "vector",
1467
+ description: "Element-wise multiplication of vectors."
1468
+ },
1469
+ {
1470
+ typeId: "vectorscale",
1471
+ names: ["vectorscale", "vscale"],
1472
+ category: "vector",
1473
+ description: "Scale a vector by a scalar value."
1474
+ },
1475
+ {
1476
+ typeId: "vectornormalize",
1477
+ names: ["vectornormalize", "vnormalize"],
1478
+ category: "vector",
1479
+ description: "Normalize a vector to unit length."
1480
+ },
1481
+ {
1482
+ typeId: "vectordot",
1483
+ names: ["vectordot", "vdot"],
1484
+ category: "vector",
1485
+ description: "Dot product of vectors A \xB7 B."
1486
+ },
1487
+ {
1488
+ typeId: "vectorlength",
1489
+ names: ["vectorlength", "vlength"],
1490
+ category: "vector",
1491
+ description: "Euclidean length of a vector."
1492
+ },
1493
+ {
1494
+ typeId: "vectorindex",
1495
+ names: ["vectorindex", "vindex"],
1496
+ category: "vector",
1497
+ description: "Extract a component from a vector."
1498
+ },
1499
+ {
1500
+ typeId: "vectorconstant",
1501
+ names: ["vectorconstant", "vconst"],
1502
+ category: "vector",
1503
+ description: "Constant vector value."
1504
+ },
1505
+ {
1506
+ typeId: "join",
1507
+ names: ["join"],
1508
+ category: "vector",
1509
+ description: "Join scalar inputs into a vector."
1510
+ },
1511
+ {
1512
+ typeId: "split",
1513
+ names: ["split"],
1514
+ category: "vector",
1515
+ description: "Split a vector into scalar outputs."
1516
+ },
1517
+ {
1518
+ typeId: "vectormin",
1519
+ names: ["vectormin"],
1520
+ category: "vector",
1521
+ description: "Minimum component in a vector."
1522
+ },
1523
+ {
1524
+ typeId: "vectormax",
1525
+ names: ["vectormax"],
1526
+ category: "vector",
1527
+ description: "Maximum component in a vector."
1528
+ },
1529
+ {
1530
+ typeId: "vectormean",
1531
+ names: ["vectormean"],
1532
+ category: "vector",
1533
+ description: "Mean of the provided vector values."
1534
+ },
1535
+ {
1536
+ typeId: "vectormedian",
1537
+ names: ["vectormedian"],
1538
+ category: "vector",
1539
+ description: "Median of the provided vector values."
1540
+ },
1541
+ {
1542
+ typeId: "vectormode",
1543
+ names: ["vectormode"],
1544
+ category: "vector",
1545
+ description: "Mode of the provided vector values."
1546
+ },
1547
+ {
1548
+ typeId: "weightedsumvector",
1549
+ names: ["weightedsumvector", "vectorsum"],
1550
+ category: "vector",
1551
+ description: "Weighted sum across vectors with optional masks."
1552
+ }
1553
+ ];
1554
+ var SCALAR_FUNCTIONS = /* @__PURE__ */ new Map();
1555
+ var SCALAR_FUNCTION_VOCABULARY = [];
1556
+ for (const config of FUNCTION_CONFIGS) {
1557
+ const signature = (0, import_metadata.requireNodeSignature)(config.typeId);
1558
+ const signatureInputs = signature.inputs;
1559
+ let inputs = signatureInputs.map((input) => ({
1560
+ id: input.id,
1561
+ optional: Boolean(input.optional),
1562
+ valueType: inferExpressionValueType(input.ty)
1563
+ }));
1564
+ let variadic = signature.variadic_inputs ? {
1565
+ id: signature.variadic_inputs.id,
1566
+ min: signature.variadic_inputs.min,
1567
+ max: typeof signature.variadic_inputs.max === "number" ? signature.variadic_inputs.max : null,
1568
+ valueType: inferExpressionValueType(signature.variadic_inputs.ty)
1569
+ } : null;
1570
+ const baseRequiredInputs = inputs.filter((input) => !input.optional).length;
1571
+ const variadicMin = variadic?.min ?? 0;
1572
+ const derivedMin = baseRequiredInputs + variadicMin;
1573
+ let derivedMax;
1574
+ if (variadic) {
1575
+ derivedMax = variadic.max === null ? null : inputs.length + (variadic.max ?? 0);
1576
+ } else {
1577
+ derivedMax = inputs.length;
1578
+ }
1579
+ let params = buildParamArgumentSpecs(signature);
1580
+ const override = FUNCTION_OVERRIDES[config.typeId];
1581
+ if (override) {
1582
+ if (override.replaceInputs) {
1583
+ inputs = override.replaceInputs.map((input) => ({ ...input }));
1584
+ } else if (override.dropTrailingInputs) {
1585
+ const dropCount = Math.min(override.dropTrailingInputs, inputs.length);
1586
+ inputs = inputs.slice(0, inputs.length - dropCount);
1587
+ }
1588
+ if ("variadic" in override) {
1589
+ variadic = override.variadic ?? null;
1590
+ }
1591
+ if (override.params) {
1592
+ params = override.params.map((param) => ({ ...param }));
1593
+ }
1594
+ }
1595
+ const requiredParamCount = params.filter((param) => !param.optional).length;
1596
+ const minArgs = override?.minArgs !== void 0 ? override.minArgs : config.minArgs !== void 0 ? config.minArgs : derivedMin + requiredParamCount;
1597
+ const maxArgs = override?.maxArgs !== void 0 ? override.maxArgs : config.maxArgs !== void 0 ? config.maxArgs : derivedMax === null ? null : derivedMax + params.length;
1598
+ const definition = {
1599
+ nodeType: config.typeId,
1600
+ inputs,
1601
+ variadic,
1602
+ params,
1603
+ minArgs,
1604
+ maxArgs,
1605
+ resultValueType: inferExpressionValueType(
1606
+ signature.outputs?.[0]?.ty ?? null
1607
+ )
1608
+ };
1609
+ if (config.typeId === "if" || config.typeId === "case") {
1610
+ const adjustedInputs = definition.inputs.map(
1611
+ (input, index) => {
1612
+ if (config.typeId === "if" && index === 0) {
1613
+ return input;
1614
+ }
1615
+ return {
1616
+ ...input,
1617
+ valueType: "any"
1618
+ };
1619
+ }
1620
+ );
1621
+ definition.inputs = adjustedInputs;
1622
+ if (definition.variadic) {
1623
+ definition.variadic = {
1624
+ ...definition.variadic,
1625
+ valueType: "any"
1626
+ };
1627
+ }
1628
+ definition.resultValueType = "any";
1629
+ }
1630
+ const names = new Set(
1631
+ [config.typeId, ...config.names].map((name) => name.toLowerCase())
1632
+ );
1633
+ names.forEach((name) => {
1634
+ SCALAR_FUNCTIONS.set(name, definition);
1635
+ });
1636
+ const orderedNames = Array.from(names);
1637
+ const displayName = orderedNames[0] ?? config.typeId;
1638
+ SCALAR_FUNCTION_VOCABULARY.push({
1639
+ name: displayName,
1640
+ aliases: orderedNames.slice(1),
1641
+ nodeType: config.typeId,
1642
+ category: config.category ?? "math",
1643
+ description: config.description
1644
+ });
1645
+ }
1646
+ function buildParamArgumentSpecs(signature) {
1647
+ if (!signature || !Array.isArray(signature.params)) {
1648
+ return [];
1649
+ }
1650
+ const params = signature.params;
1651
+ return params.map((param) => ({
1652
+ id: param.id,
1653
+ label: param.label ?? param.id,
1654
+ doc: param.doc ?? void 0,
1655
+ optional: param.default_json !== void 0,
1656
+ valueType: inferExpressionValueType(param.ty),
1657
+ min: typeof param.min === "number" ? param.min : void 0,
1658
+ max: typeof param.max === "number" ? param.max : void 0
1659
+ }));
1660
+ }
1661
+ function inferExpressionValueType(ty) {
1662
+ if (!ty) {
1663
+ return "scalar";
1664
+ }
1665
+ const normalized = ty.toLowerCase();
1666
+ if (normalized === "any") {
1667
+ return "any";
1668
+ }
1669
+ if (normalized === "vector" || normalized === "vec2" || normalized === "vec3" || normalized === "vec4" || normalized === "quat" || normalized === "colorrgba" || normalized === "transform" || normalized === "array" || normalized === "list" || normalized === "record") {
1670
+ return "vector";
1671
+ }
1672
+ if (normalized === "bool" || normalized === "boolean") {
1673
+ return "boolean";
1674
+ }
1675
+ return "scalar";
1676
+ }
1677
+
1678
+ // src/expressionVariables.ts
1679
+ var DefaultExpressionVariableTable = class {
1680
+ constructor() {
1681
+ this.variables = /* @__PURE__ */ new Map();
1682
+ this.order = [];
1683
+ }
1684
+ registerSlotVariable(options) {
1685
+ this.upsert({
1686
+ name: options.name,
1687
+ kind: "slot",
1688
+ nodeId: options.nodeId,
1689
+ metadata: {
1690
+ slotId: options.slotId,
1691
+ slotAlias: options.slotAlias,
1692
+ inputId: options.inputId,
1693
+ targetId: options.targetId,
1694
+ animatableId: options.animatableId,
1695
+ component: options.component,
1696
+ valueType: options.valueType
1697
+ }
1698
+ });
1699
+ }
1700
+ registerReservedVariable(options) {
1701
+ this.upsert({
1702
+ name: options.name,
1703
+ kind: "reserved",
1704
+ nodeId: options.nodeId,
1705
+ description: options.description,
1706
+ metadata: {
1707
+ targetId: options.targetId,
1708
+ animatableId: options.animatableId,
1709
+ component: options.component
1710
+ }
1711
+ });
1712
+ }
1713
+ resolve(name) {
1714
+ return this.variables.get(name) ?? null;
1715
+ }
1716
+ resolveNodeId(name) {
1717
+ return this.variables.get(name)?.nodeId ?? null;
1718
+ }
1719
+ entries() {
1720
+ return this.order.map((name) => this.variables.get(name)).filter((entry) => Boolean(entry));
1721
+ }
1722
+ firstNodeId() {
1723
+ for (const name of this.order) {
1724
+ const nodeId = this.variables.get(name)?.nodeId;
1725
+ if (nodeId) {
1726
+ return nodeId;
1727
+ }
1728
+ }
1729
+ return null;
1730
+ }
1731
+ missing(names) {
1732
+ const missing = [];
1733
+ for (const name of names) {
1734
+ const entry = this.variables.get(name);
1735
+ if (!entry) {
1736
+ missing.push({ name, reason: "unknown" });
1737
+ continue;
1738
+ }
1739
+ if (!entry.nodeId) {
1740
+ missing.push({
1741
+ name,
1742
+ reason: "unresolved",
1743
+ entry
1744
+ });
1745
+ }
1746
+ }
1747
+ return missing;
1748
+ }
1749
+ upsert(entry) {
1750
+ if (!this.variables.has(entry.name)) {
1751
+ this.order.push(entry.name);
1752
+ }
1753
+ this.variables.set(entry.name, entry);
1754
+ }
1755
+ };
1756
+ function createExpressionVariableTable() {
1757
+ return new DefaultExpressionVariableTable();
1758
+ }
1759
+
1760
+ // src/expressionVocabulary.ts
1761
+ var RESERVED_EXPRESSION_VARIABLES = [
1762
+ {
1763
+ name: "self",
1764
+ description: "Output of the current binding.",
1765
+ scope: "binding",
1766
+ defaultValueType: "scalar",
1767
+ available: true
1768
+ },
1769
+ {
1770
+ name: "time",
1771
+ description: "Elapsed time in seconds since the rig started.",
1772
+ scope: "graph",
1773
+ defaultValueType: "scalar",
1774
+ available: true
1775
+ },
1776
+ {
1777
+ name: "deltaTime",
1778
+ description: "Seconds elapsed since the previous frame update.",
1779
+ scope: "graph",
1780
+ defaultValueType: "scalar",
1781
+ available: true
1782
+ },
1783
+ {
1784
+ name: "frame",
1785
+ description: "Current frame counter.",
1786
+ scope: "graph",
1787
+ defaultValueType: "scalar",
1788
+ available: true
1789
+ }
1790
+ ];
1791
+ var EXPRESSION_FUNCTION_VOCABULARY = SCALAR_FUNCTION_VOCABULARY;
1792
+
1793
+ // src/ir/builder.ts
1794
+ var graphSequence = 0;
1795
+ function generateGraphId(faceId) {
1796
+ graphSequence += 1;
1797
+ return `ir_${faceId}_${graphSequence}`;
1798
+ }
1799
+ var DefaultIrGraphBuilder = class {
1800
+ constructor(options) {
1801
+ this.nodes = [];
1802
+ this.edges = [];
1803
+ this.constants = [];
1804
+ this.issues = [];
1805
+ this.faceId = options.faceId;
1806
+ this.summary = {
1807
+ faceId: options.faceId,
1808
+ inputs: [],
1809
+ outputs: [],
1810
+ bindings: []
1811
+ };
1812
+ this.metadata = {
1813
+ source: options.source ?? "ir-builder",
1814
+ registryVersion: options.registryVersion,
1815
+ generatedAt: options.generatedAt,
1816
+ annotations: options.annotations
1817
+ };
1818
+ }
1819
+ addNode(node) {
1820
+ this.nodes.push(node);
1821
+ return node;
1822
+ }
1823
+ addEdge(edge) {
1824
+ this.edges.push(edge);
1825
+ return edge;
1826
+ }
1827
+ addConstant(constant) {
1828
+ this.constants.push(constant);
1829
+ return constant;
1830
+ }
1831
+ addIssue(issue) {
1832
+ this.issues.push(issue);
1833
+ return issue;
1834
+ }
1835
+ setSummary(summary) {
1836
+ this.summary = summary;
1837
+ }
1838
+ updateMetadata(metadata) {
1839
+ this.metadata = {
1840
+ ...this.metadata,
1841
+ ...metadata
1842
+ };
1843
+ }
1844
+ build() {
1845
+ return {
1846
+ id: generateGraphId(this.faceId),
1847
+ faceId: this.faceId,
1848
+ nodes: [...this.nodes],
1849
+ edges: [...this.edges],
1850
+ constants: [...this.constants],
1851
+ issues: [...this.issues],
1852
+ summary: { ...this.summary },
1853
+ metadata: { ...this.metadata }
1854
+ };
1855
+ }
1856
+ };
1857
+ function createIrGraphBuilder(options) {
1858
+ return new DefaultIrGraphBuilder(options);
1859
+ }
1860
+ function createLegacyIrGraph(payload) {
1861
+ const builder = createIrGraphBuilder({
1862
+ faceId: payload.faceId,
1863
+ registryVersion: payload.registryVersion,
1864
+ source: payload.source ?? "legacy-graph-builder",
1865
+ annotations: payload.annotations,
1866
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
1867
+ });
1868
+ builder.setSummary(payload.summary);
1869
+ (payload.issues ?? []).forEach((issue) => builder.addIssue(issue));
1870
+ const graph = builder.build();
1871
+ return {
1872
+ ...graph,
1873
+ legacy: { spec: payload.spec }
1874
+ };
1875
+ }
1876
+ function toIrBindingSummary(summaries) {
1877
+ return summaries.map((summary) => ({
1878
+ ...summary,
1879
+ issues: summary.issues ? [...summary.issues] : void 0
1880
+ }));
1881
+ }
1882
+
1883
+ // src/ir/compiler.ts
1884
+ var import_utils2 = require("@vizij/utils");
1885
+ function compileIrGraph(graph, options = {}) {
1886
+ const preferLegacy = options.preferLegacySpec === true;
1887
+ const legacySpec = graph.legacy?.spec;
1888
+ if (preferLegacy && legacySpec) {
1889
+ return {
1890
+ spec: legacySpec,
1891
+ issues: [...graph.issues]
1892
+ };
1893
+ }
1894
+ const convertedNodes = graph.nodes.map(convertIrNodeToNodeSpec);
1895
+ const existingNodeIds = new Set(convertedNodes.map((node) => node.id));
1896
+ graph.constants.forEach((constant) => {
1897
+ if (existingNodeIds.has(constant.id)) {
1898
+ return;
1899
+ }
1900
+ convertedNodes.push(convertIrConstantToNodeSpec(constant));
1901
+ });
1902
+ const convertedEdges = graph.edges.map(convertIrEdgeToGraphEdge);
1903
+ const spec = {
1904
+ nodes: convertedNodes,
1905
+ edges: convertedEdges.length > 0 ? convertedEdges : void 0
1906
+ };
1907
+ inlineSingleUseConstants(spec);
1908
+ const metadata = extractGraphSpecMetadata(graph);
1909
+ if (metadata !== void 0) {
1910
+ spec.metadata = metadata;
1911
+ }
1912
+ return {
1913
+ spec,
1914
+ issues: [...graph.issues]
1915
+ };
1916
+ }
1917
+ function convertIrNodeToNodeSpec(node) {
1918
+ const spec = {
1919
+ id: node.id,
1920
+ type: node.type
1921
+ };
1922
+ if (node.params) {
1923
+ spec.params = cloneJsonLike(node.params);
1924
+ }
1925
+ if (node.inputDefaults) {
1926
+ spec.input_defaults = cloneJsonLike(node.inputDefaults);
1927
+ }
1928
+ if (node.metadata) {
1929
+ spec.metadata = cloneJsonLike(node.metadata);
1930
+ }
1931
+ return spec;
1932
+ }
1933
+ function convertIrConstantToNodeSpec(constant) {
1934
+ const spec = {
1935
+ id: constant.id,
1936
+ type: "constant",
1937
+ params: {
1938
+ value: constant.value
1939
+ }
1940
+ };
1941
+ if (constant.metadata) {
1942
+ spec.metadata = cloneJsonLike(constant.metadata);
1943
+ }
1944
+ return spec;
1945
+ }
1946
+ function convertIrEdgeToGraphEdge(edge) {
1947
+ return {
1948
+ from: {
1949
+ node_id: edge.from.nodeId,
1950
+ output: edge.from.portId
1951
+ },
1952
+ to: {
1953
+ node_id: edge.to.nodeId,
1954
+ input: edge.to.portId
1955
+ }
1956
+ };
1957
+ }
1958
+ function extractGraphSpecMetadata(graph) {
1959
+ const annotations = graph.metadata.annotations;
1960
+ if (!annotations?.graphSpecMetadata) {
1961
+ return void 0;
1962
+ }
1963
+ return cloneJsonLike(annotations.graphSpecMetadata);
1964
+ }
1965
+ function cloneJsonLike(value) {
1966
+ if (value === void 0 || value === null) {
1967
+ return value;
1968
+ }
1969
+ return (0, import_utils2.cloneDeepSafe)(value);
1970
+ }
1971
+ function inlineSingleUseConstants(spec) {
1972
+ const nodes = spec.nodes ?? [];
1973
+ const edges = spec.edges ? [...spec.edges] : [];
1974
+ const nodeById = new Map(nodes.map((node) => [node.id, node]));
1975
+ const constantUsage = /* @__PURE__ */ new Map();
1976
+ edges.forEach((edge) => {
1977
+ const source = nodeById.get(edge.from.node_id);
1978
+ if (source?.type === "constant") {
1979
+ constantUsage.set(source.id, (constantUsage.get(source.id) ?? 0) + 1);
1980
+ }
1981
+ });
1982
+ const updatedEdges = [];
1983
+ const constantsToRemove = /* @__PURE__ */ new Set();
1984
+ edges.forEach((edge) => {
1985
+ const source = nodeById.get(edge.from.node_id);
1986
+ if (source?.type === "constant" && constantUsage.get(source.id) === 1 && source.params && Object.prototype.hasOwnProperty.call(source.params, "value")) {
1987
+ const target = nodeById.get(edge.to.node_id);
1988
+ if (target) {
1989
+ const value = source.params.value;
1990
+ if (value !== void 0) {
1991
+ target.input_defaults = {
1992
+ ...target.input_defaults ?? {},
1993
+ [edge.to.input ?? "in"]: value
1994
+ };
1995
+ nodeById.set(target.id, target);
1996
+ constantsToRemove.add(source.id);
1997
+ return;
1998
+ }
1999
+ }
2000
+ }
2001
+ updatedEdges.push(edge);
2002
+ });
2003
+ spec.nodes = nodes.filter((node) => !constantsToRemove.has(node.id));
2004
+ spec.edges = updatedEdges.length > 0 ? updatedEdges : void 0;
2005
+ }
2006
+
2007
+ // src/bindingMetadata.ts
2008
+ var import_utils3 = require("@vizij/utils");
2009
+ function cloneOperand(entry) {
2010
+ return (0, import_utils3.cloneDeepSafe)(entry);
2011
+ }
2012
+ function describeSlot(entry) {
2013
+ const metadata = entry.metadata;
2014
+ return {
2015
+ kind: "slot",
2016
+ ref: entry.name,
2017
+ alias: metadata?.slotAlias ?? entry.name,
2018
+ slotId: metadata?.slotId,
2019
+ inputId: metadata?.inputId ?? null,
2020
+ valueType: metadata?.valueType
2021
+ };
2022
+ }
2023
+ function describeReserved(entry) {
2024
+ return {
2025
+ kind: "reserved",
2026
+ ref: entry.name,
2027
+ description: entry.description
2028
+ };
2029
+ }
2030
+ function describeReference(node, variables) {
2031
+ const entry = variables.resolve(node.name);
2032
+ if (!entry) {
2033
+ return {
2034
+ kind: "unknown",
2035
+ ref: node.name
2036
+ };
2037
+ }
2038
+ if (entry.kind === "slot") {
2039
+ return describeSlot(entry);
1353
2040
  }
1354
- isAtEnd() {
1355
- return this.index >= this.input.length;
2041
+ if (entry.kind === "reserved") {
2042
+ return describeReserved(entry);
1356
2043
  }
1357
- };
1358
- function parseControlExpression(expression) {
1359
- const parser = new ControlExpressionParser(expression);
1360
- return parser.parse();
2044
+ return {
2045
+ kind: "unknown",
2046
+ ref: entry.name
2047
+ };
1361
2048
  }
1362
- function collectExpressionReferences(node, target = /* @__PURE__ */ new Set()) {
1363
- if (!node) {
1364
- return target;
1365
- }
2049
+ function stringifyExpression(node) {
1366
2050
  switch (node.type) {
2051
+ case "Literal":
2052
+ return Number.isFinite(node.value) ? `${node.value} ` : "0";
2053
+ case "VectorLiteral":
2054
+ return `vec(${node.values.join(", ")})`;
1367
2055
  case "Reference":
1368
- target.add(node.name);
1369
- break;
2056
+ return node.name;
1370
2057
  case "Unary":
1371
- collectExpressionReferences(node.operand, target);
1372
- break;
2058
+ return `${node.operator}${stringifyExpression(node.operand)} `;
1373
2059
  case "Binary":
1374
- collectExpressionReferences(node.left, target);
1375
- collectExpressionReferences(node.right, target);
1376
- break;
2060
+ return `${stringifyExpression(node.left)} ${node.operator} ${stringifyExpression(node.right)} `;
1377
2061
  case "Function":
1378
- node.args.forEach((arg) => collectExpressionReferences(arg, target));
1379
- break;
2062
+ return `${node.name} (${node.args.map(stringifyExpression).join(", ")})`;
1380
2063
  default:
1381
- break;
2064
+ return "";
1382
2065
  }
1383
- return target;
1384
2066
  }
1385
- function mapExpression(node, visit) {
1386
- visit(node);
2067
+ function describeOperand(node, variables) {
1387
2068
  switch (node.type) {
2069
+ case "Literal":
2070
+ return {
2071
+ kind: "literal",
2072
+ literalValue: node.value
2073
+ };
2074
+ case "Reference":
2075
+ return describeReference(node, variables);
1388
2076
  case "Unary":
1389
- mapExpression(node.operand, visit);
1390
- break;
1391
2077
  case "Binary":
1392
- mapExpression(node.left, visit);
1393
- mapExpression(node.right, visit);
1394
- break;
1395
2078
  case "Function":
1396
- node.args.forEach((arg) => mapExpression(arg, visit));
1397
- break;
2079
+ case "VectorLiteral":
2080
+ return {
2081
+ kind: "expression",
2082
+ expression: stringifyExpression(node)
2083
+ };
1398
2084
  default:
1399
- break;
2085
+ return { kind: "unknown" };
1400
2086
  }
1401
2087
  }
1402
-
1403
- // src/expressionFunctions.ts
1404
- var import_metadata2 = require("@vizij/node-graph-wasm/metadata");
1405
- var FUNCTION_CONFIGS = [
1406
- { typeId: "sin", names: ["sin"] },
1407
- { typeId: "cos", names: ["cos"] },
1408
- { typeId: "tan", names: ["tan"] },
1409
- { typeId: "power", names: ["power", "pow"] },
1410
- { typeId: "log", names: ["log"] },
1411
- { typeId: "clamp", names: ["clamp"] },
1412
- { typeId: "add", names: ["add"] },
1413
- { typeId: "multiply", names: ["multiply"] },
1414
- { typeId: "subtract", names: ["subtract"] },
1415
- { typeId: "divide", names: ["divide"] },
1416
- {
1417
- typeId: "greaterthan",
1418
- names: ["greaterthan", "gt"],
1419
- minArgs: 2,
1420
- maxArgs: 2
1421
- },
1422
- { typeId: "lessthan", names: ["lessthan", "lt"], minArgs: 2, maxArgs: 2 },
1423
- { typeId: "equal", names: ["equal", "eq"], minArgs: 2, maxArgs: 2 },
1424
- {
1425
- typeId: "notequal",
1426
- names: ["notequal", "neq", "ne"],
1427
- minArgs: 2,
1428
- maxArgs: 2
1429
- },
1430
- { typeId: "and", names: ["and"], minArgs: 2, maxArgs: 2 },
1431
- { typeId: "or", names: ["or"], minArgs: 2, maxArgs: 2 },
1432
- { typeId: "xor", names: ["xor"], minArgs: 2, maxArgs: 2 },
1433
- { typeId: "not", names: ["not"], minArgs: 1, maxArgs: 1 },
1434
- { typeId: "if", names: ["if"], minArgs: 2, maxArgs: 3 },
1435
- { typeId: "time", names: ["time"], minArgs: 0, maxArgs: 0 },
1436
- { typeId: "oscillator", names: ["oscillator"], minArgs: 2, maxArgs: 2 }
1437
- ];
1438
- var SCALAR_FUNCTIONS = /* @__PURE__ */ new Map();
1439
- for (const config of FUNCTION_CONFIGS) {
1440
- const signature = (0, import_metadata2.requireNodeSignature)(config.typeId);
1441
- const signatureInputs = signature.inputs;
1442
- const inputs = signatureInputs.map((input) => ({
1443
- id: input.id,
1444
- optional: Boolean(input.optional)
1445
- }));
1446
- const variadic = signature.variadic_inputs ? {
1447
- id: signature.variadic_inputs.id,
1448
- min: signature.variadic_inputs.min,
1449
- max: signature.variadic_inputs.max ?? null
1450
- } : null;
1451
- const derivedMin = variadic ? variadic.min : inputs.filter((input) => !input.optional).length;
1452
- const derivedMax = variadic ? variadic.max : inputs.length;
1453
- const minArgs = config.minArgs ?? derivedMin;
1454
- const maxArgs = config.maxArgs !== void 0 ? config.maxArgs : derivedMax ?? null;
1455
- const definition = {
1456
- nodeType: config.typeId,
1457
- inputs,
1458
- variadic,
1459
- minArgs,
1460
- maxArgs
2088
+ function describeCaseExpression(node, variables) {
2089
+ if (node.type !== "Function") {
2090
+ return null;
2091
+ }
2092
+ if (node.name.toLowerCase() !== "case") {
2093
+ return null;
2094
+ }
2095
+ if ((node.args?.length ?? 0) < 3) {
2096
+ return null;
2097
+ }
2098
+ const [selector, defaultBranch, ...branches] = node.args;
2099
+ return {
2100
+ kind: "case",
2101
+ selector: describeOperand(selector, variables),
2102
+ defaultBranch: describeOperand(defaultBranch, variables),
2103
+ branches: branches.map((branch) => describeOperand(branch, variables))
2104
+ };
2105
+ }
2106
+ function buildBindingMetadataFromExpression(node, variables) {
2107
+ if (!node) {
2108
+ return void 0;
2109
+ }
2110
+ const caseMetadata = describeCaseExpression(node, variables);
2111
+ if (!caseMetadata) {
2112
+ return void 0;
2113
+ }
2114
+ return {
2115
+ expression: {
2116
+ case: {
2117
+ kind: "case",
2118
+ selector: caseMetadata.selector ? cloneOperand(caseMetadata.selector) : void 0,
2119
+ defaultBranch: caseMetadata.defaultBranch ? cloneOperand(caseMetadata.defaultBranch) : void 0,
2120
+ branches: caseMetadata.branches.map(cloneOperand)
2121
+ }
2122
+ }
1461
2123
  };
1462
- const names = new Set(
1463
- [config.typeId, ...config.names].map((name) => name.toLowerCase())
1464
- );
1465
- names.forEach((name) => {
1466
- SCALAR_FUNCTIONS.set(name, definition);
1467
- });
1468
2124
  }
1469
2125
 
1470
2126
  // src/graphBuilder.ts
@@ -1484,10 +2140,14 @@ function evaluateBinding({
1484
2140
  nodes,
1485
2141
  edges,
1486
2142
  constants: /* @__PURE__ */ new Map(),
1487
- counter: 0
2143
+ counter: 0,
2144
+ reservedNodes: /* @__PURE__ */ new Map(),
2145
+ nodeValueTypes: /* @__PURE__ */ new Map(),
2146
+ graphReservedNodes: context.graphReservedNodes,
2147
+ generateReservedNodeId: context.generateReservedNodeId
1488
2148
  };
1489
2149
  const targetValueType = target.valueType === "vector" ? "vector" : "scalar";
1490
- const aliasNodes = /* @__PURE__ */ new Map();
2150
+ const variableTable = createExpressionVariableTable();
1491
2151
  const slotSummaries = [];
1492
2152
  const expressionIssues = [];
1493
2153
  const rawExpression = typeof binding.expression === "string" ? binding.expression : "";
@@ -1500,10 +2160,15 @@ function evaluateBinding({
1500
2160
  const slotId = slot.id && slot.id.length > 0 ? slot.id : alias;
1501
2161
  const slotValueType = slot.valueType === "vector" ? "vector" : "scalar";
1502
2162
  let slotOutputId;
1503
- if (slot.inputId === import_utils3.SELF_BINDING_ID) {
2163
+ if (slot.inputId === import_utils5.SELF_BINDING_ID) {
1504
2164
  if (selfNodeId) {
1505
2165
  slotOutputId = selfNodeId;
1506
2166
  hasActiveSlot = true;
2167
+ setNodeValueType(
2168
+ exprContext,
2169
+ slotOutputId,
2170
+ slotValueType === "vector" ? "vector" : "scalar"
2171
+ );
1507
2172
  } else {
1508
2173
  expressionIssues.push("Self reference unavailable for this input.");
1509
2174
  slotOutputId = getConstantNodeId(exprContext, target.defaultValue);
@@ -1511,25 +2176,13 @@ function evaluateBinding({
1511
2176
  } else if (slot.inputId) {
1512
2177
  const inputNode = ensureInputNode(slot.inputId);
1513
2178
  if (inputNode) {
1514
- const remapNodeId = `remap_${safeId}_${sanitizeNodeId(slotId)}`;
1515
- nodes.push({
1516
- id: remapNodeId,
1517
- type: "centered_remap",
1518
- input_defaults: {
1519
- in_low: slot.remap.inLow,
1520
- in_anchor: slot.remap.inAnchor,
1521
- in_high: slot.remap.inHigh,
1522
- out_low: slot.remap.outLow,
1523
- out_anchor: slot.remap.outAnchor,
1524
- out_high: slot.remap.outHigh
1525
- }
1526
- });
1527
- edges.push({
1528
- from: { node_id: inputNode.nodeId },
1529
- to: { node_id: remapNodeId, input: "in" }
1530
- });
1531
- slotOutputId = remapNodeId;
2179
+ slotOutputId = inputNode.nodeId;
1532
2180
  hasActiveSlot = true;
2181
+ setNodeValueType(
2182
+ exprContext,
2183
+ slotOutputId,
2184
+ slotValueType === "vector" ? "vector" : "scalar"
2185
+ );
1533
2186
  } else {
1534
2187
  expressionIssues.push(`Missing standard input "${slot.inputId}".`);
1535
2188
  slotOutputId = getConstantNodeId(exprContext, 0);
@@ -1537,7 +2190,22 @@ function evaluateBinding({
1537
2190
  } else {
1538
2191
  slotOutputId = getConstantNodeId(exprContext, 0);
1539
2192
  }
1540
- aliasNodes.set(alias, slotOutputId);
2193
+ variableTable.registerSlotVariable({
2194
+ name: alias,
2195
+ nodeId: slotOutputId,
2196
+ slotId,
2197
+ slotAlias: alias,
2198
+ inputId: slot.inputId ?? null,
2199
+ targetId,
2200
+ animatableId,
2201
+ component,
2202
+ valueType: slotValueType
2203
+ });
2204
+ setNodeValueType(
2205
+ exprContext,
2206
+ slotOutputId,
2207
+ slotValueType === "vector" ? "vector" : "scalar"
2208
+ );
1541
2209
  slotSummaries.push({
1542
2210
  targetId,
1543
2211
  animatableId,
@@ -1545,14 +2213,31 @@ function evaluateBinding({
1545
2213
  slotId,
1546
2214
  slotAlias: alias,
1547
2215
  inputId: slot.inputId ?? null,
1548
- remap: { ...slot.remap },
1549
2216
  expression: trimmedExpression,
1550
- valueType: slotValueType
2217
+ valueType: slotValueType,
2218
+ nodeId: slotOutputId,
2219
+ expressionNodeId: slotOutputId
1551
2220
  });
1552
2221
  });
1553
2222
  if (slotSummaries.length === 0) {
1554
2223
  const alias = PRIMARY_SLOT_ALIAS;
1555
- aliasNodes.set(alias, getConstantNodeId(exprContext, 0));
2224
+ const constantId = getConstantNodeId(exprContext, 0);
2225
+ variableTable.registerSlotVariable({
2226
+ name: alias,
2227
+ nodeId: constantId,
2228
+ slotId: PRIMARY_SLOT_ID,
2229
+ slotAlias: alias,
2230
+ inputId: null,
2231
+ targetId,
2232
+ animatableId,
2233
+ component,
2234
+ valueType: targetValueType
2235
+ });
2236
+ setNodeValueType(
2237
+ exprContext,
2238
+ constantId,
2239
+ targetValueType === "vector" ? "vector" : "scalar"
2240
+ );
1556
2241
  slotSummaries.push({
1557
2242
  targetId,
1558
2243
  animatableId,
@@ -1560,28 +2245,52 @@ function evaluateBinding({
1560
2245
  slotId: PRIMARY_SLOT_ID,
1561
2246
  slotAlias: alias,
1562
2247
  inputId: null,
1563
- remap: createDefaultRemap(target),
1564
2248
  expression: trimmedExpression,
1565
- valueType: targetValueType
2249
+ valueType: targetValueType,
2250
+ nodeId: constantId,
2251
+ expressionNodeId: constantId
1566
2252
  });
1567
2253
  }
2254
+ RESERVED_EXPRESSION_VARIABLES.forEach((reserved) => {
2255
+ if (reserved.available === false) {
2256
+ return;
2257
+ }
2258
+ let nodeId = null;
2259
+ if (reserved.name === "self") {
2260
+ nodeId = selfNodeId ?? null;
2261
+ } else {
2262
+ nodeId = ensureReservedVariableNode(reserved.name, exprContext);
2263
+ }
2264
+ variableTable.registerReservedVariable({
2265
+ name: reserved.name,
2266
+ nodeId,
2267
+ description: reserved.description,
2268
+ targetId: reserved.scope === "binding" ? targetId : void 0,
2269
+ animatableId: reserved.scope === "binding" ? animatableId : void 0,
2270
+ component: reserved.scope === "binding" ? component : void 0
2271
+ });
2272
+ });
1568
2273
  const defaultAlias = slotSummaries[0]?.slotAlias ?? PRIMARY_SLOT_ALIAS;
1569
2274
  const expressionText = trimmedExpression.length > 0 ? trimmedExpression : defaultAlias;
1570
2275
  const parseResult = parseControlExpression(expressionText);
1571
2276
  let expressionAst = null;
1572
2277
  if (parseResult.node && parseResult.errors.length === 0) {
1573
- const references = collectExpressionReferences(parseResult.node);
1574
- const missing = [];
1575
- references.forEach((ref) => {
1576
- if (!aliasNodes.has(ref)) {
1577
- missing.push(ref);
1578
- }
1579
- });
2278
+ const references = Array.from(
2279
+ collectExpressionReferences(parseResult.node)
2280
+ );
2281
+ const missing = variableTable.missing(references);
1580
2282
  if (missing.length === 0) {
1581
2283
  expressionAst = parseResult.node;
1582
2284
  } else {
1583
- missing.forEach((ref) => {
1584
- expressionIssues.push(`Unknown control "${ref}".`);
2285
+ validateLiteralParamArguments(parseResult.node, expressionIssues);
2286
+ missing.forEach((missingVar) => {
2287
+ if (missingVar.reason === "unresolved" && missingVar.entry?.kind === "reserved") {
2288
+ expressionIssues.push(
2289
+ `Reserved variable "${missingVar.name}" is unavailable for this binding.`
2290
+ );
2291
+ } else {
2292
+ expressionIssues.push(`Unknown control "${missingVar.name}".`);
2293
+ }
1585
2294
  });
1586
2295
  }
1587
2296
  } else {
@@ -1589,22 +2298,30 @@ function evaluateBinding({
1589
2298
  expressionIssues.push(error.message);
1590
2299
  });
1591
2300
  }
2301
+ const expressionMetadata = buildBindingMetadataFromExpression(
2302
+ expressionAst,
2303
+ variableTable
2304
+ );
1592
2305
  let valueNodeId = null;
1593
2306
  if (expressionAst) {
1594
2307
  valueNodeId = materializeExpression(
1595
2308
  expressionAst,
1596
2309
  exprContext,
1597
- aliasNodes,
2310
+ variableTable,
1598
2311
  expressionIssues
1599
2312
  );
1600
2313
  }
1601
2314
  if (!valueNodeId) {
1602
- const fallbackAlias = aliasNodes.has(defaultAlias) ? defaultAlias : aliasNodes.keys().next().value;
1603
- valueNodeId = (fallbackAlias ? aliasNodes.get(fallbackAlias) : void 0) ?? getConstantNodeId(exprContext, 0);
2315
+ const fallbackNodeId = variableTable.resolveNodeId(defaultAlias) ?? variableTable.firstNodeId();
2316
+ valueNodeId = fallbackNodeId ?? getConstantNodeId(exprContext, 0);
1604
2317
  }
1605
2318
  const issuesCopy = expressionIssues.length ? [...new Set(expressionIssues)] : void 0;
1606
2319
  slotSummaries.forEach((summary) => {
1607
2320
  summary.expression = expressionText;
2321
+ summary.expressionNodeId = valueNodeId;
2322
+ if (expressionMetadata) {
2323
+ summary.metadata = expressionMetadata;
2324
+ }
1608
2325
  if (issuesCopy && issuesCopy.length > 0) {
1609
2326
  summary.issues = issuesCopy;
1610
2327
  const issueSet = bindingIssues.get(summary.targetId) ?? /* @__PURE__ */ new Set();
@@ -1622,8 +2339,25 @@ function sanitizeNodeId(value) {
1622
2339
  return value.replace(/[^a-zA-Z0-9_]/g, "_");
1623
2340
  }
1624
2341
  function buildRigInputPath(faceId, inputPath) {
1625
- const trimmed = inputPath.startsWith("/") ? inputPath.slice(1) : inputPath;
1626
- return `rig/${faceId}/${trimmed}`;
2342
+ let trimmed = inputPath.startsWith("/") ? inputPath.slice(1) : inputPath;
2343
+ if (!trimmed) {
2344
+ return `rig/${faceId}`;
2345
+ }
2346
+ while (trimmed.startsWith("rig/")) {
2347
+ const segments = trimmed.split("/");
2348
+ if (segments.length >= 3) {
2349
+ const existingFaceId = segments[1];
2350
+ const remainder = segments.slice(2).join("/");
2351
+ if (existingFaceId === faceId) {
2352
+ return trimmed;
2353
+ }
2354
+ trimmed = remainder || "";
2355
+ } else {
2356
+ trimmed = segments.slice(1).join("/");
2357
+ }
2358
+ }
2359
+ const suffix = trimmed ? `/${trimmed}` : "";
2360
+ return `rig/${faceId}${suffix}`;
1627
2361
  }
1628
2362
  function getComponentOrder(animatable) {
1629
2363
  switch (animatable.type) {
@@ -1679,6 +2413,34 @@ function extractComponentDefault(value, component) {
1679
2413
  }
1680
2414
  return 0;
1681
2415
  }
2416
+ function setNodeValueType(context, nodeId, valueType) {
2417
+ context.nodeValueTypes.set(nodeId, valueType);
2418
+ }
2419
+ function getNodeValueType(context, nodeId) {
2420
+ return context.nodeValueTypes.get(nodeId) ?? "any";
2421
+ }
2422
+ function matchesValueType(actual, expected) {
2423
+ if (expected === "any" || actual === "any") {
2424
+ return true;
2425
+ }
2426
+ if (expected === "scalar") {
2427
+ return actual === "scalar" || actual === "boolean";
2428
+ }
2429
+ return actual === expected;
2430
+ }
2431
+ function ensureOperandValueType(context, operandId, expected, functionName, inputId, issues) {
2432
+ if (expected === "any") {
2433
+ return;
2434
+ }
2435
+ const actual = getNodeValueType(context, operandId);
2436
+ if (matchesValueType(actual, expected)) {
2437
+ return;
2438
+ }
2439
+ const expectation = expected === "vector" ? "a vector" : expected === "boolean" ? "a boolean" : "a scalar";
2440
+ issues.push(
2441
+ `Function "${functionName}" expects ${expectation} input for "${inputId}", but the expression produced ${actual}.`
2442
+ );
2443
+ }
1682
2444
  function getConstantNodeId(context, value) {
1683
2445
  const key = Number.isFinite(value) ? value.toString() : "NaN";
1684
2446
  const existing = context.constants.get(key);
@@ -1694,6 +2456,30 @@ function getConstantNodeId(context, value) {
1694
2456
  }
1695
2457
  });
1696
2458
  context.constants.set(key, nodeId);
2459
+ setNodeValueType(context, nodeId, "scalar");
2460
+ return nodeId;
2461
+ }
2462
+ function getVectorConstantNodeId(context, values) {
2463
+ const normalized = values.map(
2464
+ (value) => Number.isFinite(value) ? value : 0
2465
+ );
2466
+ const key = `vector:${normalized.join(",")}`;
2467
+ const existing = context.constants.get(key);
2468
+ if (existing) {
2469
+ return existing;
2470
+ }
2471
+ const nodeId = `const_${context.componentSafeId}_${context.constants.size + 1}`;
2472
+ context.nodes.push({
2473
+ id: nodeId,
2474
+ type: "constant",
2475
+ params: {
2476
+ value: {
2477
+ vector: normalized
2478
+ }
2479
+ }
2480
+ });
2481
+ context.constants.set(key, nodeId);
2482
+ setNodeValueType(context, nodeId, "vector");
1697
2483
  return nodeId;
1698
2484
  }
1699
2485
  function createBinaryOperationNode(context, operator, leftId, rightId) {
@@ -1702,15 +2488,16 @@ function createBinaryOperationNode(context, operator, leftId, rightId) {
1702
2488
  id: nodeId,
1703
2489
  type: operator
1704
2490
  });
2491
+ setNodeValueType(context, nodeId, "scalar");
1705
2492
  const leftInput = operator === "subtract" || operator === "divide" ? "lhs" : "operand_1";
1706
2493
  const rightInput = operator === "subtract" || operator === "divide" ? "rhs" : "operand_2";
1707
2494
  context.edges.push({
1708
- from: { node_id: leftId },
1709
- to: { node_id: nodeId, input: leftInput }
2495
+ from: { nodeId: leftId },
2496
+ to: { nodeId, portId: leftInput }
1710
2497
  });
1711
2498
  context.edges.push({
1712
- from: { node_id: rightId },
1713
- to: { node_id: nodeId, input: rightInput }
2499
+ from: { nodeId: rightId },
2500
+ to: { nodeId, portId: rightInput }
1714
2501
  });
1715
2502
  return nodeId;
1716
2503
  }
@@ -1726,95 +2513,339 @@ function createVariadicOperationNode(context, operator, operandIds) {
1726
2513
  id: nodeId,
1727
2514
  type: operator
1728
2515
  });
2516
+ setNodeValueType(context, nodeId, "scalar");
1729
2517
  operandIds.forEach((operandId, index) => {
1730
2518
  context.edges.push({
1731
- from: { node_id: operandId },
1732
- to: { node_id: nodeId, input: `operand_${index + 1}` }
2519
+ from: { nodeId: operandId },
2520
+ to: { nodeId, portId: `operand_${index + 1}` }
1733
2521
  });
1734
2522
  });
1735
2523
  return nodeId;
1736
2524
  }
1737
- function createNamedOperationNode(context, operator, inputNames, operandIds) {
2525
+ function createNamedOperationNode(context, operator, inputNames, operandIds, resultType = "scalar", params) {
1738
2526
  const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
1739
- context.nodes.push({
2527
+ const node = {
1740
2528
  id: nodeId,
1741
2529
  type: operator
1742
- });
2530
+ };
2531
+ if (params && Object.keys(params).length > 0) {
2532
+ node.params = params;
2533
+ }
2534
+ context.nodes.push(node);
2535
+ setNodeValueType(context, nodeId, resultType);
1743
2536
  inputNames.forEach((inputName, index) => {
1744
2537
  const operandId = operandIds[index];
1745
2538
  context.edges.push({
1746
- from: { node_id: operandId },
1747
- to: { node_id: nodeId, input: inputName }
2539
+ from: { nodeId: operandId },
2540
+ to: { nodeId, portId: inputName }
2541
+ });
2542
+ });
2543
+ return nodeId;
2544
+ }
2545
+ function ensureReservedVariableNode(name, context) {
2546
+ const existing = context.reservedNodes.get(name);
2547
+ if (existing) {
2548
+ return existing;
2549
+ }
2550
+ if (name === "time" || name === "deltaTime" || name === "frame") {
2551
+ const nodeId = ensureGraphTimeNode(context);
2552
+ context.reservedNodes.set(name, nodeId);
2553
+ return nodeId;
2554
+ }
2555
+ return null;
2556
+ }
2557
+ function ensureGraphTimeNode(context) {
2558
+ const existing = context.graphReservedNodes.get("time");
2559
+ if (existing) {
2560
+ return existing;
2561
+ }
2562
+ const nodeId = context.generateReservedNodeId("time");
2563
+ context.nodes.push({
2564
+ id: nodeId,
2565
+ type: "time",
2566
+ metadata: {
2567
+ reservedVariable: "time"
2568
+ }
2569
+ });
2570
+ setNodeValueType(context, nodeId, "scalar");
2571
+ context.graphReservedNodes.set("time", nodeId);
2572
+ return nodeId;
2573
+ }
2574
+ function emitScalarFunctionNode(definition, operands, argNodes, totalArgCount, context, variables, issues, paramArgOverride) {
2575
+ if (definition.nodeType === "case") {
2576
+ return emitCaseFunctionNode(
2577
+ definition,
2578
+ operands,
2579
+ argNodes,
2580
+ context,
2581
+ variables,
2582
+ issues
2583
+ );
2584
+ }
2585
+ const orderedOperands = operands.slice(0, definition.inputs.length);
2586
+ definition.inputs.forEach((input, index) => {
2587
+ const operandId = orderedOperands[index];
2588
+ if (!operandId) {
2589
+ return;
2590
+ }
2591
+ ensureOperandValueType(
2592
+ context,
2593
+ operandId,
2594
+ input.valueType,
2595
+ definition.nodeType,
2596
+ input.id,
2597
+ issues
2598
+ );
2599
+ });
2600
+ const availableAfterInputs = Math.max(
2601
+ 0,
2602
+ totalArgCount - definition.inputs.length
2603
+ );
2604
+ const paramArgCount = definition.params.length > 0 ? Math.min(definition.params.length, availableAfterInputs) : 0;
2605
+ const variadicArgCount = definition.variadic ? Math.max(0, availableAfterInputs - paramArgCount) : 0;
2606
+ const variadicOperands = definition.variadic ? operands.slice(
2607
+ definition.inputs.length,
2608
+ definition.inputs.length + variadicArgCount
2609
+ ) : [];
2610
+ if (definition.variadic) {
2611
+ variadicOperands.forEach((operandId, _variadicIndex) => {
2612
+ ensureOperandValueType(
2613
+ context,
2614
+ operandId,
2615
+ definition.variadic.valueType,
2616
+ definition.nodeType,
2617
+ definition.variadic.id,
2618
+ issues
2619
+ );
2620
+ });
2621
+ }
2622
+ const paramArgStart = definition.inputs.length + variadicArgCount;
2623
+ const paramArgNodes = paramArgCount > 0 ? paramArgOverride ?? argNodes.slice(paramArgStart, paramArgStart + paramArgCount) : [];
2624
+ const isSlewDebugEnabled = typeof process !== "undefined" && process?.env && process.env.DEBUG_SLEW === "1";
2625
+ if (isSlewDebugEnabled && definition.nodeType === "slew") {
2626
+ console.log("slew-debug", {
2627
+ totalArgs: totalArgCount,
2628
+ inputs: definition.inputs.length,
2629
+ paramCount: definition.params.length,
2630
+ paramArgCount,
2631
+ paramArgNodesTypes: paramArgNodes.map((node) => node?.type ?? null)
2632
+ });
2633
+ }
2634
+ const nodeParams = buildParamAssignments(definition, paramArgNodes, issues);
2635
+ if (definition.variadic && definition.inputs.length === 0) {
2636
+ const nodeId2 = `expr_${context.componentSafeId}_${context.counter++}`;
2637
+ const node = {
2638
+ id: nodeId2,
2639
+ type: definition.nodeType
2640
+ };
2641
+ if (nodeParams) {
2642
+ node.params = nodeParams;
2643
+ }
2644
+ context.nodes.push(node);
2645
+ variadicOperands.forEach((operandId, index) => {
2646
+ context.edges.push({
2647
+ from: { nodeId: operandId },
2648
+ to: {
2649
+ nodeId: nodeId2,
2650
+ portId: `${definition.variadic.id}_${index + 1}`
2651
+ }
2652
+ });
2653
+ });
2654
+ setNodeValueType(context, nodeId2, definition.resultValueType);
2655
+ return nodeId2;
2656
+ }
2657
+ const providedNames = [];
2658
+ const providedOperands = [];
2659
+ definition.inputs.forEach((input, index) => {
2660
+ const operandId = orderedOperands[index];
2661
+ if (!operandId) {
2662
+ return;
2663
+ }
2664
+ providedNames.push(input.id);
2665
+ providedOperands.push(operandId);
2666
+ });
2667
+ const nodeId = createNamedOperationNode(
2668
+ context,
2669
+ definition.nodeType,
2670
+ providedNames,
2671
+ providedOperands,
2672
+ definition.resultValueType,
2673
+ nodeParams ?? void 0
2674
+ );
2675
+ if (definition.variadic) {
2676
+ variadicOperands.forEach((operandId, index) => {
2677
+ context.edges.push({
2678
+ from: { nodeId: operandId },
2679
+ to: {
2680
+ nodeId,
2681
+ portId: `${definition.variadic.id}_${index + 1}`
2682
+ }
2683
+ });
2684
+ });
2685
+ }
2686
+ return nodeId;
2687
+ }
2688
+ function emitCaseFunctionNode(definition, operands, argNodes, context, variables, issues) {
2689
+ const selectorId = operands[0];
2690
+ const defaultId = operands[1];
2691
+ const branchOperands = operands.slice(2);
2692
+ const branchArgs = argNodes.slice(2);
2693
+ if (!selectorId || branchOperands.length === 0) {
2694
+ issues.push(
2695
+ 'Function "case" requires a selector, default, and at least one branch.'
2696
+ );
2697
+ return getConstantNodeId(context, 0);
2698
+ }
2699
+ const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
2700
+ const caseLabels = branchOperands.map((_, index) => {
2701
+ const extracted = extractCaseLabel(branchArgs[index], variables);
2702
+ if (extracted) {
2703
+ return extracted;
2704
+ }
2705
+ const fallback = `case_${index + 1}`;
2706
+ issues.push(
2707
+ `Case branch ${index + 1} is missing an alias; generated fallback label ${fallback}.`
2708
+ );
2709
+ return fallback;
2710
+ });
2711
+ context.nodes.push({
2712
+ id: nodeId,
2713
+ type: definition.nodeType,
2714
+ params: caseLabels.length > 0 ? { case_labels: caseLabels } : void 0
2715
+ });
2716
+ ensureOperandValueType(
2717
+ context,
2718
+ selectorId,
2719
+ definition.inputs[0]?.valueType ?? "any",
2720
+ definition.nodeType,
2721
+ definition.inputs[0]?.id ?? "selector",
2722
+ issues
2723
+ );
2724
+ context.edges.push({
2725
+ from: { nodeId: selectorId },
2726
+ to: { nodeId, portId: definition.inputs[0]?.id ?? "selector" }
2727
+ });
2728
+ if (defaultId) {
2729
+ ensureOperandValueType(
2730
+ context,
2731
+ defaultId,
2732
+ definition.inputs[1]?.valueType ?? "any",
2733
+ definition.nodeType,
2734
+ "default",
2735
+ issues
2736
+ );
2737
+ context.edges.push({
2738
+ from: { nodeId: defaultId },
2739
+ to: { nodeId, portId: "default" }
2740
+ });
2741
+ }
2742
+ branchOperands.forEach((operandId, index) => {
2743
+ const portId = `${definition.variadic?.id ?? "operand"}_${index + 1}`;
2744
+ ensureOperandValueType(
2745
+ context,
2746
+ operandId,
2747
+ definition.variadic?.valueType ?? "any",
2748
+ definition.nodeType,
2749
+ portId,
2750
+ issues
2751
+ );
2752
+ context.edges.push({
2753
+ from: { nodeId: operandId },
2754
+ to: { nodeId, portId }
1748
2755
  });
1749
2756
  });
2757
+ setNodeValueType(context, nodeId, "any");
1750
2758
  return nodeId;
1751
2759
  }
1752
- function emitScalarFunctionNode(definition, operands, context) {
1753
- if (definition.variadic) {
1754
- const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
1755
- context.nodes.push({
1756
- id: nodeId,
1757
- type: definition.nodeType
1758
- });
1759
- operands.forEach((operandId, index) => {
1760
- context.edges.push({
1761
- from: { node_id: operandId },
1762
- to: {
1763
- node_id: nodeId,
1764
- input: `${definition.variadic.id}_${index + 1}`
1765
- }
1766
- });
1767
- });
1768
- return nodeId;
2760
+ function buildParamAssignments(definition, paramArgNodes, issues) {
2761
+ if (!definition.params.length || paramArgNodes.length === 0) {
2762
+ return null;
1769
2763
  }
1770
- const providedNames = [];
1771
- const providedOperands = [];
1772
- definition.inputs.forEach((input, index) => {
1773
- const operandId = operands[index];
1774
- if (!operandId) {
2764
+ const assignments = {};
2765
+ paramArgNodes.forEach((node, index) => {
2766
+ const spec = definition.params[index];
2767
+ if (!spec) {
1775
2768
  return;
1776
2769
  }
1777
- providedNames.push(input.id);
1778
- providedOperands.push(operandId);
2770
+ const literal = extractParamLiteral(
2771
+ node,
2772
+ spec,
2773
+ definition.nodeType,
2774
+ issues
2775
+ );
2776
+ if (literal !== null) {
2777
+ assignments[spec.id] = literal;
2778
+ }
1779
2779
  });
1780
- return createNamedOperationNode(
1781
- context,
1782
- definition.nodeType,
1783
- providedNames,
1784
- providedOperands
1785
- );
2780
+ return Object.keys(assignments).length > 0 ? assignments : null;
1786
2781
  }
1787
- function applyBindingOperators(operators, baseNodeId, safeId, nodes, edges) {
1788
- let currentNodeId = baseNodeId;
1789
- operators.forEach((operator, index) => {
1790
- if (!operator.enabled) {
1791
- return;
2782
+ function extractParamLiteral(node, spec, functionName, issues) {
2783
+ if (spec.valueType === "vector") {
2784
+ if (node.type !== "VectorLiteral") {
2785
+ issues.push(
2786
+ `Function "${functionName}" requires a literal vector for "${spec.id}".`
2787
+ );
2788
+ return null;
1792
2789
  }
1793
- let definition;
1794
- try {
1795
- definition = getBindingOperatorDefinition(operator.type);
1796
- } catch {
1797
- return;
2790
+ if (!Array.isArray(node.values) || node.values.length === 0) {
2791
+ issues.push(
2792
+ `Function "${functionName}" requires at least one value for "${spec.id}".`
2793
+ );
2794
+ return null;
1798
2795
  }
1799
- const nodeId = `${operator.type}_${safeId}_${index + 1}`;
1800
- const params = {};
1801
- definition.params.forEach((param) => {
1802
- const configured = operator.params?.[param.id];
1803
- params[param.id] = typeof configured === "number" ? configured : param.defaultValue;
1804
- });
1805
- nodes.push({
1806
- id: nodeId,
1807
- type: definition.nodeType,
1808
- params
1809
- });
1810
- const inputId = definition.inputs[0] ?? "in";
1811
- edges.push({
1812
- from: { node_id: currentNodeId },
1813
- to: { node_id: nodeId, input: inputId }
1814
- });
1815
- currentNodeId = nodeId;
1816
- });
1817
- return currentNodeId;
2796
+ return node.values.map((value) => clampScalarParamValue(value, spec));
2797
+ }
2798
+ if (node.type !== "Literal") {
2799
+ issues.push(
2800
+ `Function "${functionName}" requires a literal ${describeParamExpectation(spec.valueType)} for "${spec.id}".`
2801
+ );
2802
+ return null;
2803
+ }
2804
+ const numeric = Number(node.value);
2805
+ if (!Number.isFinite(numeric)) {
2806
+ issues.push(
2807
+ `Function "${functionName}" requires a finite ${describeParamExpectation(spec.valueType)} for "${spec.id}".`
2808
+ );
2809
+ return null;
2810
+ }
2811
+ const clamped = clampScalarParamValue(numeric, spec);
2812
+ if (spec.valueType === "boolean") {
2813
+ return clamped !== 0;
2814
+ }
2815
+ return clamped;
2816
+ }
2817
+ function clampScalarParamValue(value, spec) {
2818
+ let next = Number.isFinite(value) ? value : 0;
2819
+ if (typeof spec.min === "number" && next < spec.min) {
2820
+ next = spec.min;
2821
+ }
2822
+ if (typeof spec.max === "number" && next > spec.max) {
2823
+ next = spec.max;
2824
+ }
2825
+ return next;
2826
+ }
2827
+ function describeParamExpectation(valueType) {
2828
+ switch (valueType) {
2829
+ case "vector":
2830
+ return "vector";
2831
+ case "boolean":
2832
+ return "boolean";
2833
+ default:
2834
+ return "scalar";
2835
+ }
2836
+ }
2837
+ function extractCaseLabel(node, variables) {
2838
+ if (!node || node.type !== "Reference") {
2839
+ return null;
2840
+ }
2841
+ const entry = variables.resolve(node.name);
2842
+ if (entry && entry.metadata && "slotAlias" in entry.metadata) {
2843
+ const alias = entry.metadata.slotAlias?.trim();
2844
+ if (alias && alias.length > 0) {
2845
+ return alias;
2846
+ }
2847
+ }
2848
+ return node.name;
1818
2849
  }
1819
2850
  function collectOperands(node, operator, target) {
1820
2851
  if (node.type === "Binary" && node.operator === operator) {
@@ -1824,6 +2855,42 @@ function collectOperands(node, operator, target) {
1824
2855
  }
1825
2856
  target.push(node);
1826
2857
  }
2858
+ function validateLiteralParamArguments(node, issues) {
2859
+ if (node.type === "Function") {
2860
+ const definition = SCALAR_FUNCTIONS.get(node.name.toLowerCase());
2861
+ if (definition && definition.params.length > 0) {
2862
+ const totalArgCount = node.args.length;
2863
+ const availableAfterInputs = Math.max(
2864
+ 0,
2865
+ totalArgCount - definition.inputs.length
2866
+ );
2867
+ const paramArgCount = Math.min(
2868
+ definition.params.length,
2869
+ availableAfterInputs
2870
+ );
2871
+ const variadicArgCount = definition.variadic ? Math.max(0, availableAfterInputs - paramArgCount) : 0;
2872
+ const paramArgStart = definition.inputs.length + variadicArgCount;
2873
+ for (let index = 0; index < paramArgCount; index++) {
2874
+ const spec = definition.params[index];
2875
+ const paramNode = node.args[paramArgStart + index];
2876
+ if (!spec || !paramNode) {
2877
+ continue;
2878
+ }
2879
+ extractParamLiteral(paramNode, spec, node.name, issues);
2880
+ }
2881
+ }
2882
+ node.args.forEach((child) => validateLiteralParamArguments(child, issues));
2883
+ return;
2884
+ }
2885
+ if (node.type === "Unary") {
2886
+ validateLiteralParamArguments(node.operand, issues);
2887
+ return;
2888
+ }
2889
+ if (node.type === "Binary") {
2890
+ validateLiteralParamArguments(node.left, issues);
2891
+ validateLiteralParamArguments(node.right, issues);
2892
+ }
2893
+ }
1827
2894
  var BINARY_FUNCTION_OPERATOR_MAP = {
1828
2895
  ">": "greaterthan",
1829
2896
  "<": "lessthan",
@@ -1832,24 +2899,28 @@ var BINARY_FUNCTION_OPERATOR_MAP = {
1832
2899
  "&&": "and",
1833
2900
  "||": "or"
1834
2901
  };
1835
- function materializeExpression(node, context, aliasNodes, issues) {
2902
+ function materializeExpression(node, context, variables, issues) {
1836
2903
  switch (node.type) {
1837
2904
  case "Literal": {
1838
2905
  return getConstantNodeId(context, node.value);
1839
2906
  }
2907
+ case "VectorLiteral": {
2908
+ return getVectorConstantNodeId(context, node.values);
2909
+ }
1840
2910
  case "Reference": {
1841
- const mapped = aliasNodes.get(node.name);
1842
- if (!mapped) {
2911
+ const entry = variables.resolve(node.name);
2912
+ if (!entry) {
1843
2913
  issues.push(`Unknown control "${node.name}".`);
1844
2914
  return getConstantNodeId(context, 0);
1845
2915
  }
1846
- return mapped;
2916
+ const mappedId = entry.nodeId ?? getConstantNodeId(context, 0);
2917
+ return mappedId;
1847
2918
  }
1848
2919
  case "Unary": {
1849
2920
  const operandId = materializeExpression(
1850
2921
  node.operand,
1851
2922
  context,
1852
- aliasNodes,
2923
+ variables,
1853
2924
  issues
1854
2925
  );
1855
2926
  switch (node.operator) {
@@ -1868,7 +2939,15 @@ function materializeExpression(node, context, aliasNodes, issues) {
1868
2939
  issues.push('Function "not" is not available in metadata.');
1869
2940
  return getConstantNodeId(context, 0);
1870
2941
  }
1871
- return emitScalarFunctionNode(definition, [operandId], context);
2942
+ return emitScalarFunctionNode(
2943
+ definition,
2944
+ [operandId],
2945
+ [node.operand],
2946
+ 1,
2947
+ context,
2948
+ variables,
2949
+ issues
2950
+ );
1872
2951
  }
1873
2952
  default:
1874
2953
  issues.push("Unsupported unary operator.");
@@ -1881,7 +2960,7 @@ function materializeExpression(node, context, aliasNodes, issues) {
1881
2960
  const children = [];
1882
2961
  collectOperands(node, "+", children);
1883
2962
  const operandIds = children.map(
1884
- (child) => materializeExpression(child, context, aliasNodes, issues)
2963
+ (child) => materializeExpression(child, context, variables, issues)
1885
2964
  );
1886
2965
  return createVariadicOperationNode(context, "add", operandIds);
1887
2966
  }
@@ -1889,20 +2968,20 @@ function materializeExpression(node, context, aliasNodes, issues) {
1889
2968
  const children = [];
1890
2969
  collectOperands(node, "*", children);
1891
2970
  const operandIds = children.map(
1892
- (child) => materializeExpression(child, context, aliasNodes, issues)
2971
+ (child) => materializeExpression(child, context, variables, issues)
1893
2972
  );
1894
2973
  return createVariadicOperationNode(context, "multiply", operandIds);
1895
2974
  }
1896
2975
  const leftId = materializeExpression(
1897
2976
  node.left,
1898
2977
  context,
1899
- aliasNodes,
2978
+ variables,
1900
2979
  issues
1901
2980
  );
1902
2981
  const rightId = materializeExpression(
1903
2982
  node.right,
1904
2983
  context,
1905
- aliasNodes,
2984
+ variables,
1906
2985
  issues
1907
2986
  );
1908
2987
  if (operator === "-") {
@@ -1918,7 +2997,15 @@ function materializeExpression(node, context, aliasNodes, issues) {
1918
2997
  issues.push(`Function "${mappedFunction}" is not available.`);
1919
2998
  return getConstantNodeId(context, 0);
1920
2999
  }
1921
- return emitScalarFunctionNode(definition, [leftId, rightId], context);
3000
+ return emitScalarFunctionNode(
3001
+ definition,
3002
+ [leftId, rightId],
3003
+ [node.left, node.right],
3004
+ 2,
3005
+ context,
3006
+ variables,
3007
+ issues
3008
+ );
1922
3009
  }
1923
3010
  issues.push(`Unsupported operator "${operator}".`);
1924
3011
  return getConstantNodeId(context, 0);
@@ -1931,22 +3018,55 @@ function materializeExpression(node, context, aliasNodes, issues) {
1931
3018
  issues.push(`Unknown function "${name}".`);
1932
3019
  return getConstantNodeId(context, 0);
1933
3020
  }
1934
- const operands = node.args.map(
1935
- (arg) => materializeExpression(arg, context, aliasNodes, issues)
1936
- );
1937
- if (operands.length < definition.minArgs) {
3021
+ const argNodes = node.args;
3022
+ const argCount = argNodes.length;
3023
+ if (argCount < definition.minArgs) {
1938
3024
  issues.push(
1939
- `Function "${name}" expects at least ${definition.minArgs} arguments, received ${operands.length}.`
3025
+ `Function "${name}" expects at least ${definition.minArgs} arguments, received ${argCount}.`
1940
3026
  );
1941
3027
  return getConstantNodeId(context, 0);
1942
3028
  }
1943
- if (definition.maxArgs !== null && operands.length > definition.maxArgs) {
3029
+ if (definition.maxArgs !== null && argCount > definition.maxArgs) {
1944
3030
  issues.push(
1945
- `Function "${name}" expects at most ${definition.maxArgs} arguments, received ${operands.length}.`
3031
+ `Function "${name}" expects at most ${definition.maxArgs} arguments, received ${argCount}.`
1946
3032
  );
1947
3033
  return getConstantNodeId(context, 0);
1948
3034
  }
1949
- return emitScalarFunctionNode(definition, operands, context);
3035
+ if (name === "slew") {
3036
+ const maxRateArg = argNodes[1];
3037
+ if (!maxRateArg || maxRateArg.type !== "Literal") {
3038
+ issues.push(
3039
+ 'Function "slew" requires a literal scalar for "max_rate".'
3040
+ );
3041
+ }
3042
+ }
3043
+ const availableAfterInputs = Math.max(
3044
+ 0,
3045
+ argCount - definition.inputs.length
3046
+ );
3047
+ const paramArgCount = definition.params.length > 0 ? Math.min(definition.params.length, availableAfterInputs) : 0;
3048
+ const variadicArgCount = definition.variadic ? Math.max(0, availableAfterInputs - paramArgCount) : 0;
3049
+ const operandLimit = Math.min(
3050
+ argCount,
3051
+ definition.inputs.length + variadicArgCount
3052
+ );
3053
+ const operandNodes = argNodes.slice(0, operandLimit);
3054
+ const operands = operandNodes.map(
3055
+ (arg, _index) => materializeExpression(arg, context, variables, issues)
3056
+ );
3057
+ const paramArgStart = operandLimit;
3058
+ const paramArgNodes = paramArgCount > 0 ? argNodes.slice(paramArgStart, paramArgStart + paramArgCount) : [];
3059
+ const paramOverride = definition.nodeType === "case" ? void 0 : paramArgNodes;
3060
+ return emitScalarFunctionNode(
3061
+ definition,
3062
+ operands,
3063
+ argNodes,
3064
+ argCount,
3065
+ context,
3066
+ variables,
3067
+ issues,
3068
+ paramOverride
3069
+ );
1950
3070
  }
1951
3071
  default: {
1952
3072
  issues.push("Unsupported expression node.");
@@ -1960,10 +3080,25 @@ function buildRigGraphSpec({
1960
3080
  components,
1961
3081
  bindings,
1962
3082
  inputsById,
1963
- inputBindings
3083
+ inputBindings,
3084
+ inputMetadata
1964
3085
  }) {
3086
+ const metadataByInputId = inputMetadata ?? /* @__PURE__ */ new Map();
3087
+ const irBuilder = createIrGraphBuilder({
3088
+ faceId,
3089
+ registryVersion: import_metadata2.nodeRegistryVersion,
3090
+ source: "graphBuilder",
3091
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
3092
+ });
1965
3093
  const nodes = [];
1966
3094
  const edges = [];
3095
+ const graphReservedNodes = /* @__PURE__ */ new Map();
3096
+ const reservedNodeCounters = /* @__PURE__ */ new Map();
3097
+ const generateReservedNodeId = (kind) => {
3098
+ const nextValue = (reservedNodeCounters.get(kind) ?? 0) + 1;
3099
+ reservedNodeCounters.set(kind, nextValue);
3100
+ return `reserved_${kind}_${nextValue}`;
3101
+ };
1967
3102
  const inputNodes = /* @__PURE__ */ new Map();
1968
3103
  const buildingDerived = /* @__PURE__ */ new Set();
1969
3104
  const computedInputs = /* @__PURE__ */ new Set();
@@ -1993,7 +3128,7 @@ function buildRigGraphSpec({
1993
3128
  try {
1994
3129
  const target = bindingTargetFromInput(input);
1995
3130
  const binding = ensureBindingStructure(inputBindingRaw, target);
1996
- const requiresSelf = binding.inputId === import_utils3.SELF_BINDING_ID || binding.slots.some((slot) => slot.inputId === import_utils3.SELF_BINDING_ID);
3131
+ const requiresSelf = binding.inputId === import_utils5.SELF_BINDING_ID || binding.slots.some((slot) => slot.inputId === import_utils5.SELF_BINDING_ID);
1997
3132
  let selfNodeId;
1998
3133
  if (requiresSelf) {
1999
3134
  const sliderNodeId = `input_raw_${sanitizeNodeId(inputId)}`;
@@ -2019,7 +3154,9 @@ function buildRigGraphSpec({
2019
3154
  edges,
2020
3155
  ensureInputNode,
2021
3156
  bindingIssues,
2022
- summaryBindings
3157
+ summaryBindings,
3158
+ graphReservedNodes,
3159
+ generateReservedNodeId
2023
3160
  },
2024
3161
  selfNodeId
2025
3162
  });
@@ -2099,7 +3236,9 @@ function buildRigGraphSpec({
2099
3236
  edges,
2100
3237
  ensureInputNode,
2101
3238
  bindingIssues,
2102
- summaryBindings
3239
+ summaryBindings,
3240
+ graphReservedNodes,
3241
+ generateReservedNodeId
2103
3242
  }
2104
3243
  });
2105
3244
  valueNodeId = producedNodeId;
@@ -2115,10 +3254,11 @@ function buildRigGraphSpec({
2115
3254
  slotId: PRIMARY_SLOT_ID,
2116
3255
  slotAlias: PRIMARY_SLOT_ALIAS,
2117
3256
  inputId: null,
2118
- remap: createDefaultRemap(target),
2119
3257
  expression: PRIMARY_SLOT_ALIAS,
2120
3258
  valueType: target.valueType === "vector" ? "vector" : "scalar",
2121
- issues: ["Binding not found."]
3259
+ issues: ["Binding not found."],
3260
+ nodeId: PRIMARY_SLOT_ID,
3261
+ expressionNodeId: PRIMARY_SLOT_ID
2122
3262
  });
2123
3263
  const fallbackIssues = bindingIssues.get(component.id) ?? /* @__PURE__ */ new Set();
2124
3264
  fallbackIssues.add("Binding not found.");
@@ -2128,15 +3268,7 @@ function buildRigGraphSpec({
2128
3268
  entry.defaults.set(key, component.defaultValue);
2129
3269
  return;
2130
3270
  }
2131
- const operatorList = binding ? binding.operators ?? [] : [];
2132
- const finalNodeId = applyBindingOperators(
2133
- operatorList,
2134
- valueNodeId,
2135
- component.safeId,
2136
- nodes,
2137
- edges
2138
- );
2139
- entry.values.set(key, finalNodeId);
3271
+ entry.values.set(key, valueNodeId);
2140
3272
  });
2141
3273
  inputsById.forEach((_input, inputId) => {
2142
3274
  ensureInputNode(inputId);
@@ -2162,8 +3294,8 @@ function buildRigGraphSpec({
2162
3294
  }
2163
3295
  });
2164
3296
  edges.push({
2165
- from: { node_id: valueNodeId },
2166
- to: { node_id: outputNodeId2, input: "in" }
3297
+ from: { nodeId: valueNodeId },
3298
+ to: { nodeId: outputNodeId2, portId: "in" }
2167
3299
  });
2168
3300
  return;
2169
3301
  }
@@ -2176,7 +3308,7 @@ function buildRigGraphSpec({
2176
3308
  let sourceId = entry.values.get(componentKey);
2177
3309
  if (!sourceId) {
2178
3310
  const componentDefault = entry.defaults.get(componentKey) ?? extractComponentDefault(
2179
- (0, import_utils2.buildAnimatableValue)(entry.animatable, void 0),
3311
+ (0, import_utils4.buildAnimatableValue)(entry.animatable, void 0),
2180
3312
  componentKey
2181
3313
  );
2182
3314
  const constNodeId = `const_${safeId}_${componentKey}`;
@@ -2190,8 +3322,8 @@ function buildRigGraphSpec({
2190
3322
  sourceId = constNodeId;
2191
3323
  }
2192
3324
  edges.push({
2193
- from: { node_id: sourceId },
2194
- to: { node_id: joinNodeId, input: `operand_${index + 1}` }
3325
+ from: { nodeId: sourceId },
3326
+ to: { nodeId: joinNodeId, portId: `operand_${index + 1}` }
2195
3327
  });
2196
3328
  });
2197
3329
  const outputNodeId = `out_${safeId}`;
@@ -2203,8 +3335,8 @@ function buildRigGraphSpec({
2203
3335
  }
2204
3336
  });
2205
3337
  edges.push({
2206
- from: { node_id: joinNodeId },
2207
- to: { node_id: outputNodeId, input: "in" }
3338
+ from: { nodeId: joinNodeId },
3339
+ to: { nodeId: outputNodeId, portId: "in" }
2208
3340
  });
2209
3341
  });
2210
3342
  const nodeById = /* @__PURE__ */ new Map();
@@ -2213,7 +3345,7 @@ function buildRigGraphSpec({
2213
3345
  });
2214
3346
  const constantUsage = /* @__PURE__ */ new Map();
2215
3347
  edges.forEach((edge) => {
2216
- const source = nodeById.get(edge.from.node_id);
3348
+ const source = nodeById.get(edge.from.nodeId);
2217
3349
  if (source?.type === "constant") {
2218
3350
  constantUsage.set(source.id, (constantUsage.get(source.id) ?? 0) + 1);
2219
3351
  }
@@ -2221,15 +3353,16 @@ function buildRigGraphSpec({
2221
3353
  const updatedEdges = [];
2222
3354
  const constantsToRemove = /* @__PURE__ */ new Set();
2223
3355
  edges.forEach((edge) => {
2224
- const source = nodeById.get(edge.from.node_id);
3356
+ const source = nodeById.get(edge.from.nodeId);
2225
3357
  if (source?.type === "constant" && constantUsage.get(source.id) === 1 && source.params && Object.prototype.hasOwnProperty.call(source.params, "value")) {
2226
- const target = nodeById.get(edge.to.node_id);
3358
+ const target = nodeById.get(edge.to.nodeId);
2227
3359
  if (target) {
2228
3360
  const value = source.params.value;
2229
3361
  if (value !== void 0) {
2230
- target.input_defaults = {
2231
- ...target.input_defaults ?? {},
2232
- [edge.to.input]: value
3362
+ const targetPort = edge.to.portId ?? "in";
3363
+ target.inputDefaults = {
3364
+ ...target.inputDefaults ?? {},
3365
+ [targetPort]: value
2233
3366
  };
2234
3367
  nodeById.set(target.id, target);
2235
3368
  constantsToRemove.add(source.id);
@@ -2246,18 +3379,17 @@ function buildRigGraphSpec({
2246
3379
  const filteredSummaryBindings = summaryBindings.filter(
2247
3380
  (binding) => outputs.has(binding.animatableId) || computedInputs.has(binding.animatableId)
2248
3381
  );
2249
- const spec = {
2250
- nodes: filteredNodes,
2251
- edges: updatedEdges.length ? updatedEdges : void 0
2252
- };
2253
- const baseSpec = spec;
2254
- const specWithMetadata = {
2255
- ...baseSpec,
2256
- metadata: {
2257
- ...baseSpec.metadata,
2258
- vizij: {
2259
- faceId,
2260
- inputs: Array.from(inputsById.values()).map((input) => ({
3382
+ const vizijMetadata = {
3383
+ vizij: {
3384
+ faceId,
3385
+ inputs: Array.from(inputsById.values()).map((input) => {
3386
+ const meta = metadataByInputId.get(input.id);
3387
+ const derivedRoot = meta?.root ?? input.group;
3388
+ let derivedSource = meta?.source;
3389
+ if (!derivedSource && input.path.startsWith("/standard/")) {
3390
+ derivedSource = "preset";
3391
+ }
3392
+ const entry = {
2261
3393
  id: input.id,
2262
3394
  path: input.path,
2263
3395
  sourceId: input.sourceId,
@@ -2268,15 +3400,22 @@ function buildRigGraphSpec({
2268
3400
  min: input.range.min,
2269
3401
  max: input.range.max
2270
3402
  }
2271
- })),
2272
- bindings: filteredSummaryBindings.map((binding) => ({
2273
- ...binding,
2274
- remap: { ...binding.remap },
2275
- expression: binding.expression,
2276
- valueType: binding.valueType,
2277
- issues: binding.issues ? [...binding.issues] : void 0
2278
- }))
2279
- }
3403
+ };
3404
+ if (derivedSource) {
3405
+ entry.source = derivedSource;
3406
+ }
3407
+ if (derivedRoot) {
3408
+ entry.root = derivedRoot;
3409
+ }
3410
+ return entry;
3411
+ }),
3412
+ bindings: filteredSummaryBindings.map((binding) => ({
3413
+ ...binding,
3414
+ expression: binding.expression,
3415
+ valueType: binding.valueType,
3416
+ issues: binding.issues ? [...binding.issues] : void 0,
3417
+ metadata: binding.metadata ? cloneJsonLike2(binding.metadata) : void 0
3418
+ }))
2280
3419
  }
2281
3420
  };
2282
3421
  const issuesByTarget = {};
@@ -2291,29 +3430,148 @@ function buildRigGraphSpec({
2291
3430
  issues.forEach((issue) => fatalIssues.add(issue));
2292
3431
  });
2293
3432
  remapDefaultIssues.forEach((issue) => fatalIssues.add(issue));
3433
+ const summaryPayload = {
3434
+ faceId,
3435
+ inputs: Array.from(inputNodes.values()).map(
3436
+ ({ input }) => buildRigInputPath(faceId, input.path)
3437
+ ),
3438
+ outputs: [...dynamicOutputs, ...computedInputList],
3439
+ bindings: filteredSummaryBindings
3440
+ };
3441
+ const irSummary = {
3442
+ faceId: summaryPayload.faceId,
3443
+ inputs: [...summaryPayload.inputs],
3444
+ outputs: [...summaryPayload.outputs],
3445
+ bindings: toIrBindingSummary(
3446
+ filteredSummaryBindings.map((binding) => ({
3447
+ ...binding,
3448
+ issues: binding.issues ? [...binding.issues] : void 0
3449
+ }))
3450
+ )
3451
+ };
3452
+ const fatalIssueList = Array.from(fatalIssues);
3453
+ const irIssues = createIrIssuesFromLegacy(issuesByTarget, fatalIssueList);
3454
+ filteredNodes.forEach((node) => {
3455
+ irBuilder.addNode(cloneIrNode(node));
3456
+ const constant = extractIrConstantFromNode(node);
3457
+ if (constant) {
3458
+ irBuilder.addConstant(constant);
3459
+ }
3460
+ });
3461
+ updatedEdges.forEach((edge) => {
3462
+ irBuilder.addEdge(cloneIrEdge(edge));
3463
+ });
3464
+ irBuilder.setSummary(irSummary);
3465
+ irIssues.forEach((issue) => irBuilder.addIssue(issue));
3466
+ irBuilder.updateMetadata({
3467
+ annotations: {
3468
+ graphSpecMetadata: cloneJsonLike2(vizijMetadata)
3469
+ }
3470
+ });
3471
+ const irGraph = irBuilder.build();
3472
+ const compiled = compileIrGraph(irGraph, { preferLegacySpec: false });
2294
3473
  return {
2295
- spec: specWithMetadata,
2296
- summary: {
2297
- faceId,
2298
- inputs: Array.from(inputNodes.values()).map(
2299
- ({ input }) => buildRigInputPath(faceId, input.path)
2300
- ),
2301
- outputs: [...dynamicOutputs, ...computedInputList],
2302
- bindings: filteredSummaryBindings
2303
- },
3474
+ spec: compiled.spec,
3475
+ summary: summaryPayload,
2304
3476
  issues: {
2305
3477
  byTarget: issuesByTarget,
2306
- fatal: Array.from(fatalIssues)
3478
+ fatal: fatalIssueList
3479
+ },
3480
+ ir: {
3481
+ graph: irGraph,
3482
+ compile: (options) => compileIrGraph(irGraph, options)
3483
+ }
3484
+ };
3485
+ }
3486
+ function createIrIssuesFromLegacy(issuesByTarget, fatalIssues) {
3487
+ const fatalSet = new Set(fatalIssues);
3488
+ const collected = [];
3489
+ let counter = 0;
3490
+ Object.entries(issuesByTarget).forEach(([targetId, messages]) => {
3491
+ messages.forEach((message) => {
3492
+ counter += 1;
3493
+ collected.push({
3494
+ id: `issue_${counter}`,
3495
+ severity: fatalSet.has(message) ? "error" : "warning",
3496
+ message,
3497
+ targetId,
3498
+ tags: fatalSet.has(message) ? ["fatal"] : void 0
3499
+ });
3500
+ });
3501
+ });
3502
+ fatalSet.forEach((message) => {
3503
+ const alreadyCaptured = collected.some(
3504
+ (issue) => issue.message === message
3505
+ );
3506
+ if (alreadyCaptured) {
3507
+ return;
2307
3508
  }
3509
+ counter += 1;
3510
+ collected.push({
3511
+ id: `issue_${counter}`,
3512
+ severity: "error",
3513
+ message,
3514
+ tags: ["fatal"]
3515
+ });
3516
+ });
3517
+ return collected;
3518
+ }
3519
+ function cloneIrNode(node) {
3520
+ return {
3521
+ id: node.id,
3522
+ type: node.type,
3523
+ category: node.category,
3524
+ label: node.label,
3525
+ description: node.description,
3526
+ params: cloneJsonLike2(node.params),
3527
+ inputDefaults: cloneJsonLike2(node.inputDefaults),
3528
+ metadata: cloneJsonLike2(node.metadata)
3529
+ };
3530
+ }
3531
+ function cloneIrEdge(edge) {
3532
+ return {
3533
+ id: edge.id,
3534
+ from: {
3535
+ nodeId: edge.from.nodeId,
3536
+ portId: edge.from.portId,
3537
+ component: edge.from.component
3538
+ },
3539
+ to: {
3540
+ nodeId: edge.to.nodeId,
3541
+ portId: edge.to.portId,
3542
+ component: edge.to.component
3543
+ },
3544
+ metadata: cloneJsonLike2(edge.metadata)
3545
+ };
3546
+ }
3547
+ function extractIrConstantFromNode(node) {
3548
+ if (node.type !== "constant") {
3549
+ return null;
3550
+ }
3551
+ const value = node.params?.value;
3552
+ if (typeof value !== "number" || !Number.isFinite(value)) {
3553
+ return null;
3554
+ }
3555
+ return {
3556
+ id: node.id,
3557
+ value,
3558
+ valueType: "scalar",
3559
+ metadata: cloneJsonLike2(node.metadata)
2308
3560
  };
2309
3561
  }
3562
+ function cloneJsonLike2(value) {
3563
+ if (value === void 0 || value === null) {
3564
+ return value;
3565
+ }
3566
+ return (0, import_utils4.cloneDeepSafe)(value);
3567
+ }
2310
3568
  function validateRemapDefaults(nodes) {
2311
3569
  const issues = [];
2312
3570
  nodes.forEach((node) => {
2313
3571
  if (node.type !== "centered_remap") {
2314
3572
  return;
2315
3573
  }
2316
- const defaults = node.input_defaults ?? {};
3574
+ const defaults = node.inputDefaults ?? {};
2317
3575
  [
2318
3576
  "in_low",
2319
3577
  "in_anchor",
@@ -2330,38 +3588,423 @@ function validateRemapDefaults(nodes) {
2330
3588
  });
2331
3589
  return issues;
2332
3590
  }
3591
+
3592
+ // src/ir/inspection.ts
3593
+ var import_metadata3 = require("@vizij/node-graph-wasm/metadata");
3594
+ var import_utils6 = require("@vizij/utils");
3595
+ var MACHINE_REPORT_VERSION = 1;
3596
+ var DEFAULT_DIFF_LIMIT = 50;
3597
+ function buildMachineReport(result) {
3598
+ return {
3599
+ reportVersion: MACHINE_REPORT_VERSION,
3600
+ faceId: result.summary.faceId,
3601
+ summary: normalizeSummary(result.summary),
3602
+ issues: normalizeIssues(result.issues),
3603
+ irGraph: result.ir?.graph ? normalizeIrGraph(result.ir.graph) : void 0
3604
+ };
3605
+ }
3606
+ function diffMachineReports(actual, expected, options) {
3607
+ const limit = normalizeDiffLimit(options?.limit);
3608
+ const ctx = {
3609
+ differences: [],
3610
+ limit,
3611
+ limitReached: false
3612
+ };
3613
+ diffValues(actual, expected, "$", ctx);
3614
+ return {
3615
+ equal: ctx.differences.length === 0,
3616
+ differences: ctx.differences,
3617
+ limitReached: ctx.limitReached
3618
+ };
3619
+ }
3620
+ function normalizeSummary(summary) {
3621
+ return {
3622
+ faceId: summary.faceId,
3623
+ inputs: [...summary.inputs].sort(),
3624
+ outputs: [...summary.outputs].sort(),
3625
+ bindings: normalizeGraphBindingSummaries(summary.bindings)
3626
+ };
3627
+ }
3628
+ function normalizeGraphBindingSummaries(bindings) {
3629
+ const normalized = bindings.map((binding) => ({
3630
+ targetId: binding.targetId,
3631
+ animatableId: binding.animatableId,
3632
+ component: binding.component ?? void 0,
3633
+ slotId: binding.slotId,
3634
+ slotAlias: binding.slotAlias,
3635
+ inputId: binding.inputId ?? null,
3636
+ expression: binding.expression,
3637
+ valueType: binding.valueType,
3638
+ nodeId: binding.nodeId,
3639
+ expressionNodeId: binding.expressionNodeId,
3640
+ issues: normalizeStringArray(binding.issues),
3641
+ metadata: cloneBindingMetadata2(binding.metadata)
3642
+ }));
3643
+ normalized.sort((a, b) => bindingSortKey(a).localeCompare(bindingSortKey(b)));
3644
+ return normalized;
3645
+ }
3646
+ function cloneBindingMetadata2(metadata) {
3647
+ if (!metadata) {
3648
+ return void 0;
3649
+ }
3650
+ return (0, import_utils6.cloneDeepSafe)(metadata);
3651
+ }
3652
+ function normalizeIssues(issues) {
3653
+ const byTargetEntries = Object.entries(issues.byTarget ?? {}).map(
3654
+ ([targetId, messages]) => [targetId, [...messages].sort()]
3655
+ );
3656
+ byTargetEntries.sort(([a], [b]) => a.localeCompare(b));
3657
+ const byTarget = {};
3658
+ byTargetEntries.forEach(([targetId, messages]) => {
3659
+ byTarget[targetId] = messages;
3660
+ });
3661
+ return {
3662
+ fatal: [...issues.fatal].sort(),
3663
+ byTarget
3664
+ };
3665
+ }
3666
+ function normalizeIrGraph(graph) {
3667
+ return {
3668
+ id: graph.id,
3669
+ faceId: graph.faceId,
3670
+ nodes: normalizeIrNodes(graph.nodes),
3671
+ edges: normalizeIrEdges(graph.edges),
3672
+ constants: normalizeIrConstants(graph.constants),
3673
+ issues: normalizeIrIssues(graph.issues),
3674
+ summary: normalizeIrGraphSummary(graph.summary),
3675
+ metadata: normalizeIrMetadata(graph.metadata)
3676
+ };
3677
+ }
3678
+ function normalizeIrNodes(nodes) {
3679
+ return [...nodes].map((node) => ({
3680
+ ...node,
3681
+ inputDefaults: node.inputDefaults ? sortPlainObject(node.inputDefaults) : void 0,
3682
+ params: node.params ? sortPlainObject(node.params) : void 0,
3683
+ metadata: node.metadata ? sortPlainObject(node.metadata) : void 0,
3684
+ annotations: buildNodeAnnotations(node)
3685
+ })).sort((a, b) => a.id.localeCompare(b.id));
3686
+ }
3687
+ function normalizeIrEdges(edges) {
3688
+ return [...edges].map((edge) => ({
3689
+ ...edge,
3690
+ from: normalizePortRef(edge.from),
3691
+ to: normalizePortRef(edge.to),
3692
+ metadata: edge.metadata ? sortPlainObject(edge.metadata) : void 0
3693
+ })).sort((a, b) => edgeSortKey(a).localeCompare(edgeSortKey(b)));
3694
+ }
3695
+ function normalizePortRef(edgeRef) {
3696
+ return {
3697
+ nodeId: edgeRef.nodeId,
3698
+ portId: edgeRef.portId ?? void 0,
3699
+ component: edgeRef.component ?? void 0
3700
+ };
3701
+ }
3702
+ function edgeSortKey(edge) {
3703
+ if (edge.id) {
3704
+ return edge.id;
3705
+ }
3706
+ const fromPort = edge.from.portId ?? "";
3707
+ const toPort = edge.to.portId ?? "";
3708
+ return `${edge.from.nodeId}:${fromPort}->${edge.to.nodeId}:${toPort}`;
3709
+ }
3710
+ function normalizeIrConstants(constants) {
3711
+ return [...constants].map((constant) => ({
3712
+ ...constant,
3713
+ metadata: constant.metadata ? sortPlainObject(constant.metadata) : void 0
3714
+ })).sort((a, b) => a.id.localeCompare(b.id));
3715
+ }
3716
+ function normalizeIrIssues(issues) {
3717
+ return [...issues].map((issue) => ({
3718
+ ...issue,
3719
+ tags: normalizeStringArray(issue.tags),
3720
+ details: issue.details ? sortPlainObject(issue.details) : void 0
3721
+ })).sort((a, b) => a.id.localeCompare(b.id));
3722
+ }
3723
+ function normalizeIrGraphSummary(summary) {
3724
+ return {
3725
+ faceId: summary.faceId,
3726
+ inputs: [...summary.inputs].sort(),
3727
+ outputs: [...summary.outputs].sort(),
3728
+ bindings: normalizeIrBindingSummaries(summary.bindings)
3729
+ };
3730
+ }
3731
+ function normalizeIrBindingSummaries(bindings) {
3732
+ const normalized = bindings.map((binding) => ({
3733
+ ...binding,
3734
+ issues: normalizeStringArray(binding.issues)
3735
+ }));
3736
+ normalized.sort((a, b) => bindingSortKey(a).localeCompare(bindingSortKey(b)));
3737
+ return normalized;
3738
+ }
3739
+ function normalizeIrMetadata(metadata) {
3740
+ const normalized = {
3741
+ source: metadata.source ?? "unknown"
3742
+ };
3743
+ if (metadata.registryVersion) {
3744
+ normalized.registryVersion = metadata.registryVersion;
3745
+ }
3746
+ if (metadata.annotations) {
3747
+ const sorted = sortPlainObject(metadata.annotations);
3748
+ if (Object.keys(sorted).length > 0) {
3749
+ normalized.annotations = sorted;
3750
+ }
3751
+ }
3752
+ return normalized;
3753
+ }
3754
+ function buildNodeAnnotations(node) {
3755
+ const signature = findRegistrySignature(node.type);
3756
+ if (!signature) {
3757
+ return void 0;
3758
+ }
3759
+ return {
3760
+ registry: normalizeRegistrySignature(signature)
3761
+ };
3762
+ }
3763
+ function findRegistrySignature(typeId) {
3764
+ try {
3765
+ const signature = (0, import_metadata3.findNodeSignature)(typeId);
3766
+ if (!signature) {
3767
+ return void 0;
3768
+ }
3769
+ return signature;
3770
+ } catch {
3771
+ return void 0;
3772
+ }
3773
+ }
3774
+ function normalizeRegistrySignature(signature) {
3775
+ return {
3776
+ typeId: signature.type_id,
3777
+ name: signature.name,
3778
+ category: signature.category,
3779
+ doc: signature.doc,
3780
+ inputs: signature.inputs.map(normalizeRegistryPortSpec),
3781
+ variadicInputs: signature.variadic_inputs ? normalizeRegistryVariadicSpec(signature.variadic_inputs) : void 0,
3782
+ outputs: signature.outputs.map(normalizeRegistryPortSpec),
3783
+ variadicOutputs: signature.variadic_outputs ? normalizeRegistryVariadicSpec(signature.variadic_outputs) : void 0,
3784
+ params: signature.params.map(normalizeRegistryParamSpec)
3785
+ };
3786
+ }
3787
+ function normalizeRegistryPortSpec(port) {
3788
+ const normalized = {
3789
+ id: port.id,
3790
+ label: port.label,
3791
+ type: port.ty ?? "unknown"
3792
+ };
3793
+ if (port.doc) {
3794
+ normalized.doc = port.doc;
3795
+ }
3796
+ if (port.optional) {
3797
+ normalized.optional = true;
3798
+ }
3799
+ return normalized;
3800
+ }
3801
+ function normalizeRegistryVariadicSpec(spec) {
3802
+ const normalized = {
3803
+ id: spec.id,
3804
+ label: spec.label,
3805
+ type: spec.ty ?? "unknown",
3806
+ min: spec.min
3807
+ };
3808
+ if (spec.doc) {
3809
+ normalized.doc = spec.doc;
3810
+ }
3811
+ if (typeof spec.max === "number") {
3812
+ normalized.max = spec.max;
3813
+ }
3814
+ return normalized;
3815
+ }
3816
+ function normalizeRegistryParamSpec(param) {
3817
+ const normalized = {
3818
+ id: param.id,
3819
+ label: param.label,
3820
+ type: param.ty ?? "unknown"
3821
+ };
3822
+ if (param.doc) {
3823
+ normalized.doc = param.doc;
3824
+ }
3825
+ if (param.default_json !== void 0) {
3826
+ normalized.defaultValue = normalizePlainValue(param.default_json);
3827
+ }
3828
+ if (typeof param.min === "number") {
3829
+ normalized.min = param.min;
3830
+ }
3831
+ if (typeof param.max === "number") {
3832
+ normalized.max = param.max;
3833
+ }
3834
+ return normalized;
3835
+ }
3836
+ function normalizeStringArray(values) {
3837
+ if (!values || values.length === 0) {
3838
+ return void 0;
3839
+ }
3840
+ const unique = Array.from(new Set(values));
3841
+ unique.sort();
3842
+ return unique;
3843
+ }
3844
+ function bindingSortKey(binding) {
3845
+ const component = binding.component ?? "";
3846
+ return `${binding.targetId}::${component}::${binding.slotId}::${binding.slotAlias}::${binding.animatableId}`;
3847
+ }
3848
+ function sortPlainObject(record) {
3849
+ const sorted = {};
3850
+ Object.keys(record).sort().forEach((key) => {
3851
+ sorted[key] = normalizePlainValue(record[key]);
3852
+ });
3853
+ return sorted;
3854
+ }
3855
+ function normalizePlainValue(value) {
3856
+ if (Array.isArray(value)) {
3857
+ return value.map((entry) => normalizePlainValue(entry));
3858
+ }
3859
+ if (isPlainObject(value)) {
3860
+ return sortPlainObject(value);
3861
+ }
3862
+ return value;
3863
+ }
3864
+ function isPlainObject(value) {
3865
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3866
+ }
3867
+ function diffValues(actual, expected, path, ctx) {
3868
+ if (ctx.differences.length >= ctx.limit) {
3869
+ ctx.limitReached = true;
3870
+ return;
3871
+ }
3872
+ if (Object.is(actual, expected)) {
3873
+ return;
3874
+ }
3875
+ if (Array.isArray(actual) && Array.isArray(expected)) {
3876
+ const compareLength = Math.min(actual.length, expected.length);
3877
+ for (let index = 0; index < compareLength; index += 1) {
3878
+ diffValues(actual[index], expected[index], pathIndex(path, index), ctx);
3879
+ if (ctx.differences.length >= ctx.limit) {
3880
+ ctx.limitReached = true;
3881
+ return;
3882
+ }
3883
+ }
3884
+ if (actual.length > expected.length) {
3885
+ for (let index = compareLength; index < actual.length; index += 1) {
3886
+ pushDifference(ctx, {
3887
+ kind: "unexpected",
3888
+ path: pathIndex(path, index),
3889
+ actual: actual[index]
3890
+ });
3891
+ if (ctx.differences.length >= ctx.limit) {
3892
+ ctx.limitReached = true;
3893
+ return;
3894
+ }
3895
+ }
3896
+ } else if (expected.length > actual.length) {
3897
+ for (let index = compareLength; index < expected.length; index += 1) {
3898
+ pushDifference(ctx, {
3899
+ kind: "missing",
3900
+ path: pathIndex(path, index),
3901
+ expected: expected[index]
3902
+ });
3903
+ if (ctx.differences.length >= ctx.limit) {
3904
+ ctx.limitReached = true;
3905
+ return;
3906
+ }
3907
+ }
3908
+ }
3909
+ return;
3910
+ }
3911
+ if (isPlainObject(actual) && isPlainObject(expected)) {
3912
+ const keys = /* @__PURE__ */ new Set([...Object.keys(actual), ...Object.keys(expected)]);
3913
+ const sortedKeys = Array.from(keys).sort();
3914
+ for (const key of sortedKeys) {
3915
+ if (!(key in actual)) {
3916
+ pushDifference(ctx, {
3917
+ kind: "missing",
3918
+ path: pathKey(path, key),
3919
+ expected: expected[key]
3920
+ });
3921
+ } else if (!(key in expected)) {
3922
+ pushDifference(ctx, {
3923
+ kind: "unexpected",
3924
+ path: pathKey(path, key),
3925
+ actual: actual[key]
3926
+ });
3927
+ } else {
3928
+ diffValues(actual[key], expected[key], pathKey(path, key), ctx);
3929
+ }
3930
+ if (ctx.differences.length >= ctx.limit) {
3931
+ ctx.limitReached = true;
3932
+ return;
3933
+ }
3934
+ }
3935
+ return;
3936
+ }
3937
+ pushDifference(ctx, {
3938
+ kind: "mismatch",
3939
+ path,
3940
+ actual,
3941
+ expected
3942
+ });
3943
+ }
3944
+ function pathKey(base, key) {
3945
+ if (base === "") {
3946
+ return key;
3947
+ }
3948
+ if (base.endsWith("]")) {
3949
+ return `${base}.${key}`;
3950
+ }
3951
+ return `${base}.${key}`;
3952
+ }
3953
+ function pathIndex(base, index) {
3954
+ return `${base}[${index}]`;
3955
+ }
3956
+ function pushDifference(ctx, entry) {
3957
+ if (ctx.differences.length < ctx.limit) {
3958
+ ctx.differences.push(entry);
3959
+ if (ctx.differences.length === ctx.limit) {
3960
+ ctx.limitReached = true;
3961
+ }
3962
+ } else {
3963
+ ctx.limitReached = true;
3964
+ }
3965
+ }
3966
+ function normalizeDiffLimit(limit) {
3967
+ if (typeof limit !== "number" || !Number.isFinite(limit) || limit <= 0) {
3968
+ return DEFAULT_DIFF_LIMIT;
3969
+ }
3970
+ return Math.floor(limit);
3971
+ }
2333
3972
  // Annotate the CommonJS export names for ESM import in node:
2334
3973
  0 && (module.exports = {
3974
+ EXPRESSION_FUNCTION_VOCABULARY,
3975
+ MACHINE_REPORT_VERSION,
2335
3976
  PRIMARY_SLOT_ALIAS,
2336
3977
  PRIMARY_SLOT_ID,
3978
+ RESERVED_EXPRESSION_VARIABLES,
3979
+ SCALAR_FUNCTIONS,
3980
+ SCALAR_FUNCTION_VOCABULARY,
2337
3981
  addBindingSlot,
2338
3982
  bindingFromDefinition,
2339
- bindingOperatorDefinitions,
2340
- bindingOperatorTypes,
2341
3983
  bindingTargetFromComponent,
2342
3984
  bindingTargetFromInput,
2343
3985
  bindingToDefinition,
3986
+ buildCanonicalBindingExpression,
3987
+ buildMachineReport,
2344
3988
  buildRigGraphSpec,
2345
3989
  collectExpressionReferences,
3990
+ compileIrGraph,
2346
3991
  createDefaultBinding,
2347
3992
  createDefaultBindings,
2348
3993
  createDefaultInputValues,
2349
- createDefaultOperatorSettings,
2350
3994
  createDefaultParentBinding,
2351
- createDefaultRemap,
3995
+ createExpressionVariableTable,
3996
+ createIrGraphBuilder,
3997
+ createLegacyIrGraph,
3998
+ diffMachineReports,
2352
3999
  ensureBindingStructure,
2353
- ensureOperatorParams,
2354
- getBindingOperatorDefinition,
2355
4000
  getPrimaryBindingSlot,
2356
4001
  mapExpression,
2357
4002
  parseControlExpression,
2358
4003
  reconcileBindings,
2359
- remapValue,
2360
4004
  removeBindingSlot,
2361
- setBindingOperatorEnabled,
4005
+ toIrBindingSummary,
2362
4006
  updateBindingExpression,
2363
- updateBindingOperatorParam,
2364
4007
  updateBindingSlotAlias,
2365
- updateBindingSlotRemap,
4008
+ updateBindingSlotValueType,
2366
4009
  updateBindingWithInput
2367
4010
  });