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