@vizij/node-graph-authoring 0.0.4 → 0.0.5

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