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