@vizij/node-graph-authoring 0.1.0 → 0.1.1

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.
@@ -1942,7 +1942,16 @@ function inlineSingleUseConstants(spec) {
1942
1942
  }
1943
1943
 
1944
1944
  // src/graphBuilder.ts
1945
- import { buildAnimatableValue, cloneDeepSafe as cloneDeepSafe4 } from "@vizij/utils";
1945
+ import {
1946
+ RIG_PIPELINE_V1_VERSION,
1947
+ buildAnimatableValue,
1948
+ cloneDeepSafe as cloneDeepSafe4,
1949
+ hasRigPipelineV1InputConfig,
1950
+ isRigElementStandardInputPath,
1951
+ normalizeStandardRigInputPath,
1952
+ resolveRigPipelineV1InputConfig,
1953
+ resolveStandardRigInputId
1954
+ } from "@vizij/utils";
1946
1955
  import { SELF_BINDING_ID as SELF_BINDING_ID2 } from "@vizij/utils";
1947
1956
  import { nodeRegistryVersion } from "@vizij/node-graph-wasm/metadata";
1948
1957
 
@@ -2068,6 +2077,61 @@ function buildBindingMetadataFromExpression(node, variables) {
2068
2077
  }
2069
2078
 
2070
2079
  // src/graphBuilder.ts
2080
+ function resolveBindingSlotInputId(bindingInputId, inputsById) {
2081
+ if (!bindingInputId || bindingInputId === SELF_BINDING_ID2) {
2082
+ return bindingInputId;
2083
+ }
2084
+ const resolvedInputId = resolveStandardRigInputId(bindingInputId, inputsById);
2085
+ if (inputsById.has(resolvedInputId)) {
2086
+ return resolvedInputId;
2087
+ }
2088
+ return bindingInputId;
2089
+ }
2090
+ function isRigElementAliasInput(inputId, inputsById) {
2091
+ const input = inputsById.get(inputId);
2092
+ if (!input?.path) {
2093
+ return false;
2094
+ }
2095
+ return isRigElementStandardInputPath(input.path);
2096
+ }
2097
+ function isHigherOrderRigBindingInput(inputId, inputsById) {
2098
+ const input = inputsById.get(inputId);
2099
+ if (!input?.path) {
2100
+ return false;
2101
+ }
2102
+ if (isRigElementAliasInput(inputId, inputsById)) {
2103
+ return false;
2104
+ }
2105
+ return true;
2106
+ }
2107
+ function bindingReferencesRigElementInput(binding, inputsById) {
2108
+ const candidateInputIds = /* @__PURE__ */ new Set();
2109
+ if (binding.inputId && binding.inputId !== SELF_BINDING_ID2) {
2110
+ candidateInputIds.add(binding.inputId);
2111
+ }
2112
+ binding.slots.forEach((slot) => {
2113
+ if (slot.inputId && slot.inputId !== SELF_BINDING_ID2) {
2114
+ candidateInputIds.add(slot.inputId);
2115
+ }
2116
+ });
2117
+ for (const candidateInputId of candidateInputIds) {
2118
+ if (isRigElementStandardInputPath(candidateInputId)) {
2119
+ return true;
2120
+ }
2121
+ const resolvedCandidateId = resolveBindingSlotInputId(
2122
+ candidateInputId,
2123
+ inputsById
2124
+ );
2125
+ if (!resolvedCandidateId || resolvedCandidateId === SELF_BINDING_ID2) {
2126
+ continue;
2127
+ }
2128
+ const resolvedInput = inputsById.get(resolvedCandidateId);
2129
+ if (resolvedInput && isRigElementStandardInputPath(resolvedInput.path)) {
2130
+ return true;
2131
+ }
2132
+ }
2133
+ return false;
2134
+ }
2071
2135
  function evaluateBinding({
2072
2136
  binding,
2073
2137
  target,
@@ -2076,9 +2140,18 @@ function evaluateBinding({
2076
2140
  component,
2077
2141
  safeId,
2078
2142
  context,
2079
- selfNodeId
2143
+ selfNodeId,
2144
+ enforceRigBoundaryRules = false
2080
2145
  }) {
2081
- const { nodes, edges, ensureInputNode, bindingIssues, summaryBindings } = context;
2146
+ const {
2147
+ nodes,
2148
+ edges,
2149
+ ensureInputNode,
2150
+ bindingIssues,
2151
+ summaryBindings,
2152
+ inputsById,
2153
+ inputBindings
2154
+ } = context;
2082
2155
  const exprContext = {
2083
2156
  componentSafeId: safeId,
2084
2157
  nodes,
@@ -2102,6 +2175,10 @@ function evaluateBinding({
2102
2175
  const fallbackAlias = `s${index + 1}`;
2103
2176
  const alias = aliasBase.length > 0 ? aliasBase : fallbackAlias;
2104
2177
  const slotId = slot.id && slot.id.length > 0 ? slot.id : alias;
2178
+ const resolvedSlotInputId = resolveBindingSlotInputId(
2179
+ slot.inputId,
2180
+ inputsById
2181
+ );
2105
2182
  const slotValueType = slot.valueType === "vector" ? "vector" : "scalar";
2106
2183
  let slotOutputId;
2107
2184
  if (slot.inputId === SELF_BINDING_ID2) {
@@ -2117,19 +2194,30 @@ function evaluateBinding({
2117
2194
  expressionIssues.push("Self reference unavailable for this input.");
2118
2195
  slotOutputId = getConstantNodeId(exprContext, target.defaultValue);
2119
2196
  }
2120
- } else if (slot.inputId) {
2121
- const inputNode = ensureInputNode(slot.inputId);
2122
- if (inputNode) {
2123
- slotOutputId = inputNode.nodeId;
2124
- hasActiveSlot = true;
2125
- setNodeValueType(
2126
- exprContext,
2127
- slotOutputId,
2128
- slotValueType === "vector" ? "vector" : "scalar"
2197
+ } else if (resolvedSlotInputId) {
2198
+ const inputId = resolvedSlotInputId;
2199
+ const sourceBinding = inputBindings[inputId];
2200
+ const allowedHigherOrderViaRigElementSource = sourceBinding !== void 0 && bindingReferencesRigElementInput(sourceBinding, inputsById);
2201
+ if (enforceRigBoundaryRules && isHigherOrderRigBindingInput(inputId, inputsById) && !allowedHigherOrderViaRigElementSource && inputId !== SELF_BINDING_ID2) {
2202
+ expressionIssues.push(
2203
+ `Input "${inputId}" is a higher-order rig input and cannot directly drive animatable "${target.id}".`
2129
2204
  );
2130
- } else {
2131
- expressionIssues.push(`Missing standard input "${slot.inputId}".`);
2132
2205
  slotOutputId = getConstantNodeId(exprContext, 0);
2206
+ hasActiveSlot = true;
2207
+ } else {
2208
+ const inputNode = ensureInputNode(inputId);
2209
+ if (inputNode) {
2210
+ slotOutputId = inputNode.nodeId;
2211
+ hasActiveSlot = true;
2212
+ setNodeValueType(
2213
+ exprContext,
2214
+ slotOutputId,
2215
+ slotValueType === "vector" ? "vector" : "scalar"
2216
+ );
2217
+ } else {
2218
+ expressionIssues.push(`Missing standard input "${inputId}".`);
2219
+ slotOutputId = getConstantNodeId(exprContext, 0);
2220
+ }
2133
2221
  }
2134
2222
  } else {
2135
2223
  slotOutputId = getConstantNodeId(exprContext, 0);
@@ -2139,7 +2227,7 @@ function evaluateBinding({
2139
2227
  nodeId: slotOutputId,
2140
2228
  slotId,
2141
2229
  slotAlias: alias,
2142
- inputId: slot.inputId ?? null,
2230
+ inputId: resolvedSlotInputId ?? null,
2143
2231
  targetId,
2144
2232
  animatableId,
2145
2233
  component,
@@ -2156,7 +2244,7 @@ function evaluateBinding({
2156
2244
  component,
2157
2245
  slotId,
2158
2246
  slotAlias: alias,
2159
- inputId: slot.inputId ?? null,
2247
+ inputId: resolvedSlotInputId ?? null,
2160
2248
  expression: trimmedExpression,
2161
2249
  valueType: slotValueType,
2162
2250
  nodeId: slotOutputId,
@@ -2303,6 +2391,20 @@ function buildRigInputPath(faceId, inputPath) {
2303
2391
  const suffix = trimmed ? `/${trimmed}` : "";
2304
2392
  return `rig/${faceId}${suffix}`;
2305
2393
  }
2394
+ function buildPoseControlInputPath(faceId, inputId) {
2395
+ return `rig/${faceId}/pose/control/${inputId}`;
2396
+ }
2397
+ function isPoseWeightInputPath(path) {
2398
+ const normalized = normalizeStandardRigInputPath(path);
2399
+ return normalized.startsWith("/poses/") && normalized.endsWith(".weight");
2400
+ }
2401
+ function isPoseControlPath(path) {
2402
+ const normalized = normalizeStandardRigInputPath(path);
2403
+ return normalized.startsWith("/pose/control/");
2404
+ }
2405
+ function resolveInputComposeMode(mode) {
2406
+ return mode === "average" ? "average" : "add";
2407
+ }
2306
2408
  function getComponentOrder(animatable) {
2307
2409
  switch (animatable.type) {
2308
2410
  case "vector2":
@@ -3025,7 +3127,9 @@ function buildRigGraphSpec({
3025
3127
  bindings,
3026
3128
  inputsById,
3027
3129
  inputBindings,
3028
- inputMetadata
3130
+ inputMetadata,
3131
+ inputComposeModesById,
3132
+ pipelineV1
3029
3133
  }) {
3030
3134
  const metadataByInputId = inputMetadata ?? /* @__PURE__ */ new Map();
3031
3135
  const irBuilder = createIrGraphBuilder({
@@ -3048,8 +3152,634 @@ function buildRigGraphSpec({
3048
3152
  const computedInputs = /* @__PURE__ */ new Set();
3049
3153
  const summaryBindings = [];
3050
3154
  const bindingIssues = /* @__PURE__ */ new Map();
3155
+ const stagedPipelineByInputId = /* @__PURE__ */ new Map();
3051
3156
  const animatableEntries = /* @__PURE__ */ new Map();
3052
3157
  const outputs = /* @__PURE__ */ new Set();
3158
+ const composeModeByInputId = /* @__PURE__ */ new Map();
3159
+ Object.entries(inputComposeModesById ?? {}).forEach(([inputId, mode]) => {
3160
+ if (!inputsById.has(inputId)) {
3161
+ return;
3162
+ }
3163
+ composeModeByInputId.set(inputId, resolveInputComposeMode(mode));
3164
+ });
3165
+ const shouldComposeInputWithPoseControl = (input) => {
3166
+ if (!composeModeByInputId.has(input.id)) {
3167
+ return false;
3168
+ }
3169
+ if (isPoseWeightInputPath(input.path)) {
3170
+ return false;
3171
+ }
3172
+ if (isPoseControlPath(input.path)) {
3173
+ return false;
3174
+ }
3175
+ return true;
3176
+ };
3177
+ const buildNormalizedAdditiveBlendNodeId = ({
3178
+ nodeIdPrefix,
3179
+ sourceNodeIds,
3180
+ baseline
3181
+ }) => {
3182
+ if (sourceNodeIds.length === 0) {
3183
+ const fallbackNodeId = `${nodeIdPrefix}_baseline`;
3184
+ nodes.push({
3185
+ id: fallbackNodeId,
3186
+ type: "constant",
3187
+ params: {
3188
+ value: baseline
3189
+ }
3190
+ });
3191
+ return fallbackNodeId;
3192
+ }
3193
+ if (sourceNodeIds.length === 1) {
3194
+ return sourceNodeIds[0];
3195
+ }
3196
+ const addNodeId = `${nodeIdPrefix}_add`;
3197
+ nodes.push({
3198
+ id: addNodeId,
3199
+ type: "add"
3200
+ });
3201
+ sourceNodeIds.forEach((sourceNodeId, index) => {
3202
+ edges.push({
3203
+ from: { nodeId: sourceNodeId },
3204
+ to: { nodeId: addNodeId, portId: `operand_${index + 1}` }
3205
+ });
3206
+ });
3207
+ const normalizedNodeId = `${nodeIdPrefix}_normalized_add`;
3208
+ nodes.push({
3209
+ id: normalizedNodeId,
3210
+ type: "subtract",
3211
+ inputDefaults: {
3212
+ rhs: (sourceNodeIds.length - 1) * baseline
3213
+ }
3214
+ });
3215
+ edges.push({
3216
+ from: { nodeId: addNodeId },
3217
+ to: { nodeId: normalizedNodeId, portId: "lhs" }
3218
+ });
3219
+ return normalizedNodeId;
3220
+ };
3221
+ const splitTopLevelCommaSeparated = (value) => {
3222
+ const segments = [];
3223
+ let depthParen = 0;
3224
+ let depthBracket = 0;
3225
+ let start = 0;
3226
+ for (let index = 0; index < value.length; index += 1) {
3227
+ const char = value[index];
3228
+ if (char === "(") {
3229
+ depthParen += 1;
3230
+ continue;
3231
+ }
3232
+ if (char === ")") {
3233
+ depthParen = Math.max(0, depthParen - 1);
3234
+ continue;
3235
+ }
3236
+ if (char === "[") {
3237
+ depthBracket += 1;
3238
+ continue;
3239
+ }
3240
+ if (char === "]") {
3241
+ depthBracket = Math.max(0, depthBracket - 1);
3242
+ continue;
3243
+ }
3244
+ if (char === "," && depthParen === 0 && depthBracket === 0) {
3245
+ segments.push(value.slice(start, index));
3246
+ start = index + 1;
3247
+ }
3248
+ }
3249
+ segments.push(value.slice(start));
3250
+ return segments;
3251
+ };
3252
+ const stripTopLevelAssignment = (value) => {
3253
+ let depthParen = 0;
3254
+ let depthBracket = 0;
3255
+ for (let index = 0; index < value.length; index += 1) {
3256
+ const char = value[index];
3257
+ if (char === "(") {
3258
+ depthParen += 1;
3259
+ continue;
3260
+ }
3261
+ if (char === ")") {
3262
+ depthParen = Math.max(0, depthParen - 1);
3263
+ continue;
3264
+ }
3265
+ if (char === "[") {
3266
+ depthBracket += 1;
3267
+ continue;
3268
+ }
3269
+ if (char === "]") {
3270
+ depthBracket = Math.max(0, depthBracket - 1);
3271
+ continue;
3272
+ }
3273
+ if (char !== "=" || depthParen !== 0 || depthBracket !== 0) {
3274
+ continue;
3275
+ }
3276
+ const previous = value[index - 1];
3277
+ const next = value[index + 1];
3278
+ if (previous === "=" || next === "=") {
3279
+ continue;
3280
+ }
3281
+ return value.slice(index + 1).trim();
3282
+ }
3283
+ return value.trim();
3284
+ };
3285
+ const isNormalizedAdditiveFunctionName = (value) => {
3286
+ const normalized = value.trim().toLowerCase();
3287
+ return normalized === "normalizedadditive" || normalized === "normalizedaddative" || normalized === "noramalizedadditive" || normalized === "noramalizedaddative";
3288
+ };
3289
+ const buildNormalizedAdditiveExpression = (value) => {
3290
+ const args = splitTopLevelCommaSeparated(value).map(
3291
+ (entry) => entry.trim()
3292
+ );
3293
+ if (args.length === 0) {
3294
+ return "default";
3295
+ }
3296
+ const parentTerms = [];
3297
+ let baselineExpression = "default";
3298
+ const firstArg = args[0];
3299
+ if (firstArg && firstArg.startsWith("[") && firstArg.endsWith("]")) {
3300
+ const inner = firstArg.slice(1, -1).trim();
3301
+ if (inner.length > 0) {
3302
+ splitTopLevelCommaSeparated(inner).forEach((entry) => {
3303
+ const term = entry.trim();
3304
+ if (term.length > 0) {
3305
+ parentTerms.push(term);
3306
+ }
3307
+ });
3308
+ }
3309
+ args.slice(1).forEach((entry) => {
3310
+ const baselineMatch = entry.match(/^baseline\s*=\s*(.+)$/i);
3311
+ if (baselineMatch?.[1]) {
3312
+ baselineExpression = baselineMatch[1].trim();
3313
+ return;
3314
+ }
3315
+ const term = entry.trim();
3316
+ if (term.length > 0) {
3317
+ parentTerms.push(term);
3318
+ }
3319
+ });
3320
+ } else {
3321
+ args.forEach((entry) => {
3322
+ const baselineMatch = entry.match(/^baseline\s*=\s*(.+)$/i);
3323
+ if (baselineMatch?.[1]) {
3324
+ baselineExpression = baselineMatch[1].trim();
3325
+ return;
3326
+ }
3327
+ const term = entry.trim();
3328
+ if (term.length > 0) {
3329
+ parentTerms.push(term);
3330
+ }
3331
+ });
3332
+ }
3333
+ if (parentTerms.length === 0) {
3334
+ return `(${baselineExpression})`;
3335
+ }
3336
+ if (parentTerms.length === 1) {
3337
+ return `(${parentTerms[0]})`;
3338
+ }
3339
+ return `((${parentTerms.join(" + ")}) - (${parentTerms.length - 1}) * (${baselineExpression}))`;
3340
+ };
3341
+ const rewriteNormalizedAdditiveCalls = (value) => {
3342
+ let cursor = 0;
3343
+ let rewritten = "";
3344
+ while (cursor < value.length) {
3345
+ const remaining = value.slice(cursor);
3346
+ const match = remaining.match(
3347
+ /(normalizedadditive|normalizedaddative|noramalizedadditive|noramalizedaddative)\s*\(/i
3348
+ );
3349
+ if (!match || match.index === void 0) {
3350
+ rewritten += remaining;
3351
+ break;
3352
+ }
3353
+ const matchStart = cursor + match.index;
3354
+ const functionName = match[1] ?? "";
3355
+ const openParenIndex = value.indexOf(
3356
+ "(",
3357
+ matchStart + functionName.length
3358
+ );
3359
+ if (openParenIndex < 0) {
3360
+ rewritten += value.slice(cursor);
3361
+ break;
3362
+ }
3363
+ rewritten += value.slice(cursor, matchStart);
3364
+ let depth = 1;
3365
+ let closeParenIndex = openParenIndex + 1;
3366
+ while (closeParenIndex < value.length && depth > 0) {
3367
+ const char = value[closeParenIndex];
3368
+ if (char === "(") {
3369
+ depth += 1;
3370
+ } else if (char === ")") {
3371
+ depth -= 1;
3372
+ }
3373
+ closeParenIndex += 1;
3374
+ }
3375
+ if (depth !== 0) {
3376
+ rewritten += value.slice(matchStart);
3377
+ break;
3378
+ }
3379
+ const argsContent = value.slice(openParenIndex + 1, closeParenIndex - 1);
3380
+ if (isNormalizedAdditiveFunctionName(functionName)) {
3381
+ rewritten += buildNormalizedAdditiveExpression(argsContent);
3382
+ } else {
3383
+ rewritten += value.slice(matchStart, closeParenIndex);
3384
+ }
3385
+ cursor = closeParenIndex;
3386
+ }
3387
+ return rewritten;
3388
+ };
3389
+ const normalizeStagedFormulaExpression = (expression) => {
3390
+ const rhs = stripTopLevelAssignment(expression);
3391
+ return rewriteNormalizedAdditiveCalls(rhs);
3392
+ };
3393
+ const normalizeFormulaSignature = (expression) => normalizeStagedFormulaExpression(expression).replace(/\s+/g, "").toLowerCase();
3394
+ const buildDefaultParentTransformNodeId = (params) => {
3395
+ let transformedNodeId = params.sourceNodeId;
3396
+ if (params.scale !== 1) {
3397
+ const scaledNodeId = `input_parent_scale_${params.nodeSuffix}`;
3398
+ nodes.push({
3399
+ id: scaledNodeId,
3400
+ type: "multiply",
3401
+ inputDefaults: {
3402
+ operand_2: params.scale
3403
+ }
3404
+ });
3405
+ edges.push({
3406
+ from: { nodeId: transformedNodeId },
3407
+ to: { nodeId: scaledNodeId, portId: "operand_1" }
3408
+ });
3409
+ transformedNodeId = scaledNodeId;
3410
+ }
3411
+ if (params.offset !== 0) {
3412
+ const offsetNodeId = `input_parent_offset_${params.nodeSuffix}`;
3413
+ nodes.push({
3414
+ id: offsetNodeId,
3415
+ type: "add",
3416
+ inputDefaults: {
3417
+ operand_2: params.offset
3418
+ }
3419
+ });
3420
+ edges.push({
3421
+ from: { nodeId: transformedNodeId },
3422
+ to: { nodeId: offsetNodeId, portId: "operand_1" }
3423
+ });
3424
+ transformedNodeId = offsetNodeId;
3425
+ }
3426
+ return transformedNodeId;
3427
+ };
3428
+ const buildStagedFormulaNodeId = (params) => {
3429
+ const normalizedExpression = normalizeStagedFormulaExpression(
3430
+ params.expression
3431
+ );
3432
+ if (!normalizedExpression) {
3433
+ return params.fallbackNodeId;
3434
+ }
3435
+ const parseResult = parseControlExpression(normalizedExpression);
3436
+ const issues = [];
3437
+ if (!parseResult.node) {
3438
+ parseResult.errors.forEach((error) => {
3439
+ issues.push(
3440
+ `${params.issuePrefix}: ${error.message} (index ${error.index}).`
3441
+ );
3442
+ });
3443
+ }
3444
+ if (!parseResult.node || issues.length > 0) {
3445
+ const issueSet = bindingIssues.get(params.inputId) ?? /* @__PURE__ */ new Set();
3446
+ issues.forEach((issue) => issueSet.add(issue));
3447
+ if (issues.length > 0) {
3448
+ bindingIssues.set(params.inputId, issueSet);
3449
+ }
3450
+ return params.fallbackNodeId;
3451
+ }
3452
+ const exprContext = {
3453
+ componentSafeId: params.componentSafeId,
3454
+ nodes,
3455
+ edges,
3456
+ constants: /* @__PURE__ */ new Map(),
3457
+ counter: 1,
3458
+ reservedNodes: /* @__PURE__ */ new Map(),
3459
+ nodeValueTypes: /* @__PURE__ */ new Map(),
3460
+ graphReservedNodes,
3461
+ generateReservedNodeId
3462
+ };
3463
+ const variableTable = createExpressionVariableTable();
3464
+ const registerVariableName = (name, nodeId2) => {
3465
+ const trimmed = name.trim();
3466
+ if (!trimmed) {
3467
+ return;
3468
+ }
3469
+ variableTable.registerReservedVariable({
3470
+ name: trimmed,
3471
+ nodeId: nodeId2,
3472
+ description: "Staged pipeline formula variable"
3473
+ });
3474
+ const lower = trimmed.toLowerCase();
3475
+ if (lower !== trimmed) {
3476
+ variableTable.registerReservedVariable({
3477
+ name: lower,
3478
+ nodeId: nodeId2,
3479
+ description: "Staged pipeline formula variable"
3480
+ });
3481
+ }
3482
+ };
3483
+ Object.entries(params.variables).forEach(([name, variable]) => {
3484
+ const nodeId2 = variable.nodeId ?? (typeof variable.value === "number" && Number.isFinite(variable.value) ? getConstantNodeId(exprContext, variable.value) : null);
3485
+ if (!nodeId2) {
3486
+ return;
3487
+ }
3488
+ registerVariableName(name, nodeId2);
3489
+ });
3490
+ const references = collectExpressionReferences(parseResult.node);
3491
+ const missingVariables = variableTable.missing(references);
3492
+ if (missingVariables.length > 0) {
3493
+ const issueSet = bindingIssues.get(params.inputId) ?? /* @__PURE__ */ new Set();
3494
+ missingVariables.forEach((entry) => {
3495
+ issueSet.add(
3496
+ `${params.issuePrefix}: unknown formula variable "${entry.name}".`
3497
+ );
3498
+ });
3499
+ bindingIssues.set(params.inputId, issueSet);
3500
+ return params.fallbackNodeId;
3501
+ }
3502
+ validateLiteralParamArguments(parseResult.node, issues);
3503
+ const nodeId = materializeExpression(
3504
+ parseResult.node,
3505
+ exprContext,
3506
+ variableTable,
3507
+ issues
3508
+ );
3509
+ if (issues.length > 0) {
3510
+ const issueSet = bindingIssues.get(params.inputId) ?? /* @__PURE__ */ new Set();
3511
+ issues.forEach(
3512
+ (issue) => issueSet.add(`${params.issuePrefix}: ${issue}`)
3513
+ );
3514
+ bindingIssues.set(params.inputId, issueSet);
3515
+ return params.fallbackNodeId;
3516
+ }
3517
+ return nodeId;
3518
+ };
3519
+ const buildLegacyEffectiveInputNodeId = (input, directNodeId) => {
3520
+ if (!shouldComposeInputWithPoseControl(input)) {
3521
+ return directNodeId;
3522
+ }
3523
+ const safeInputId = sanitizeNodeId(input.id);
3524
+ const composeBaseline = Number.isFinite(input.defaultValue) ? input.defaultValue : 0;
3525
+ const poseControlNodeId = `input_pose_control_${safeInputId}`;
3526
+ nodes.push({
3527
+ id: poseControlNodeId,
3528
+ type: "input",
3529
+ params: {
3530
+ path: buildPoseControlInputPath(faceId, input.id),
3531
+ value: { float: composeBaseline }
3532
+ }
3533
+ });
3534
+ const composeAddNodeId = `input_compose_add_${safeInputId}`;
3535
+ nodes.push({
3536
+ id: composeAddNodeId,
3537
+ type: "add"
3538
+ });
3539
+ edges.push(
3540
+ {
3541
+ from: { nodeId: directNodeId },
3542
+ to: { nodeId: composeAddNodeId, portId: "operand_1" }
3543
+ },
3544
+ {
3545
+ from: { nodeId: poseControlNodeId },
3546
+ to: { nodeId: composeAddNodeId, portId: "operand_2" }
3547
+ }
3548
+ );
3549
+ const composeMode = composeModeByInputId.get(input.id) ?? "add";
3550
+ let composeOutputNodeId = composeAddNodeId;
3551
+ if (composeMode === "average") {
3552
+ composeOutputNodeId = `input_compose_average_${safeInputId}`;
3553
+ nodes.push({
3554
+ id: composeOutputNodeId,
3555
+ type: "divide",
3556
+ inputDefaults: { rhs: 2 }
3557
+ });
3558
+ edges.push({
3559
+ from: { nodeId: composeAddNodeId },
3560
+ to: { nodeId: composeOutputNodeId, portId: "lhs" }
3561
+ });
3562
+ } else {
3563
+ composeOutputNodeId = `input_compose_normalized_add_${safeInputId}`;
3564
+ nodes.push({
3565
+ id: composeOutputNodeId,
3566
+ type: "subtract",
3567
+ inputDefaults: { rhs: composeBaseline }
3568
+ });
3569
+ edges.push({
3570
+ from: { nodeId: composeAddNodeId },
3571
+ to: { nodeId: composeOutputNodeId, portId: "lhs" }
3572
+ });
3573
+ }
3574
+ const minValue = Number.isFinite(input.range.min) ? input.range.min : -1;
3575
+ const maxValue = Number.isFinite(input.range.max) ? input.range.max : 1;
3576
+ const clampNodeId = `input_effective_${safeInputId}`;
3577
+ nodes.push({
3578
+ id: clampNodeId,
3579
+ type: "clamp",
3580
+ inputDefaults: { min: minValue, max: maxValue }
3581
+ });
3582
+ edges.push({
3583
+ from: { nodeId: composeOutputNodeId },
3584
+ to: { nodeId: clampNodeId, portId: "in" }
3585
+ });
3586
+ return clampNodeId;
3587
+ };
3588
+ const buildStagedEffectiveInputNodeId = (input, stagedConfig) => {
3589
+ const safeInputId = sanitizeNodeId(input.id);
3590
+ const composeBaseline = Number.isFinite(input.defaultValue) ? input.defaultValue : 0;
3591
+ const parentContributionNodes = [];
3592
+ const parentNodeIdByAlias = /* @__PURE__ */ new Map();
3593
+ stagedConfig.parents.forEach((parent, index) => {
3594
+ if (!parent.enabled) {
3595
+ return;
3596
+ }
3597
+ const resolvedParentInputId = resolveStandardRigInputId(
3598
+ parent.inputId,
3599
+ inputsById
3600
+ );
3601
+ const parentInput = ensureInputNode(resolvedParentInputId);
3602
+ if (!parentInput) {
3603
+ const issueSet = bindingIssues.get(input.id) ?? /* @__PURE__ */ new Set();
3604
+ issueSet.add(
3605
+ `Staged parent "${resolvedParentInputId}" missing for "${input.id}".`
3606
+ );
3607
+ bindingIssues.set(input.id, issueSet);
3608
+ return;
3609
+ }
3610
+ const nodeSuffix = `${safeInputId}_${index + 1}`;
3611
+ const fallbackParentNodeId = buildDefaultParentTransformNodeId({
3612
+ sourceNodeId: parentInput.nodeId,
3613
+ nodeSuffix,
3614
+ scale: parent.scale,
3615
+ offset: parent.offset
3616
+ });
3617
+ const defaultParentFormulaExpression = `${parent.alias} = parent * scale + offset`;
3618
+ const parentFormulaNodeId = normalizeFormulaSignature(parent.expression) === normalizeFormulaSignature(defaultParentFormulaExpression) ? fallbackParentNodeId : buildStagedFormulaNodeId({
3619
+ expression: parent.expression,
3620
+ fallbackNodeId: fallbackParentNodeId,
3621
+ componentSafeId: `staged_parent_${nodeSuffix}`,
3622
+ inputId: input.id,
3623
+ issuePrefix: `Parent formula "${parent.alias}"`,
3624
+ variables: {
3625
+ parent: { nodeId: parentInput.nodeId },
3626
+ scale: { value: parent.scale },
3627
+ offset: { value: parent.offset },
3628
+ default: { value: composeBaseline },
3629
+ baseline: { value: composeBaseline }
3630
+ }
3631
+ });
3632
+ parentContributionNodes.push(parentFormulaNodeId);
3633
+ parentNodeIdByAlias.set(parent.alias, parentFormulaNodeId);
3634
+ const normalizedAlias = parent.alias.toLowerCase();
3635
+ if (normalizedAlias !== parent.alias) {
3636
+ parentNodeIdByAlias.set(normalizedAlias, parentFormulaNodeId);
3637
+ }
3638
+ });
3639
+ const parentContributionNodeId = parentContributionNodes.length > 0 ? (() => {
3640
+ const defaultParentContributionNodeId = buildNormalizedAdditiveBlendNodeId({
3641
+ nodeIdPrefix: `input_parent_blend_${safeInputId}`,
3642
+ sourceNodeIds: parentContributionNodes,
3643
+ baseline: composeBaseline
3644
+ });
3645
+ const defaultParentContributionExpression = `parentContribution = normalizedAdditive([${stagedConfig.parents.filter((entry) => entry.enabled).map((entry) => entry.alias).join(", ")}], baseline=default)`;
3646
+ if (normalizeFormulaSignature(stagedConfig.parentBlend.expression) === normalizeFormulaSignature(defaultParentContributionExpression)) {
3647
+ return defaultParentContributionNodeId;
3648
+ }
3649
+ return buildStagedFormulaNodeId({
3650
+ expression: stagedConfig.parentBlend.expression,
3651
+ fallbackNodeId: defaultParentContributionNodeId,
3652
+ componentSafeId: `staged_parent_contribution_${safeInputId}`,
3653
+ inputId: input.id,
3654
+ issuePrefix: "Parent contribution formula",
3655
+ variables: {
3656
+ ...Object.fromEntries(
3657
+ Array.from(parentNodeIdByAlias.entries()).map(
3658
+ ([alias, nodeId]) => [alias, { nodeId }]
3659
+ )
3660
+ ),
3661
+ default: { value: composeBaseline },
3662
+ baseline: { value: composeBaseline }
3663
+ }
3664
+ });
3665
+ })() : null;
3666
+ let poseContributionNodeId = null;
3667
+ const hasPoseContribution = stagedConfig.poseSource.targetIds.length > 0 || shouldComposeInputWithPoseControl(input);
3668
+ if (hasPoseContribution) {
3669
+ poseContributionNodeId = `input_pose_control_${safeInputId}`;
3670
+ nodes.push({
3671
+ id: poseContributionNodeId,
3672
+ type: "input",
3673
+ params: {
3674
+ path: buildPoseControlInputPath(faceId, input.id),
3675
+ value: { float: composeBaseline }
3676
+ }
3677
+ });
3678
+ }
3679
+ const sourceBranchNodeIds = [];
3680
+ if (parentContributionNodeId) {
3681
+ sourceBranchNodeIds.push(parentContributionNodeId);
3682
+ }
3683
+ if (poseContributionNodeId) {
3684
+ sourceBranchNodeIds.push(poseContributionNodeId);
3685
+ }
3686
+ if (stagedConfig.directInput.enabled) {
3687
+ const directNodeId = `input_direct_${safeInputId}`;
3688
+ nodes.push({
3689
+ id: directNodeId,
3690
+ type: "input",
3691
+ params: {
3692
+ path: stagedConfig.directInput.valuePath,
3693
+ value: { float: composeBaseline }
3694
+ }
3695
+ });
3696
+ sourceBranchNodeIds.push(directNodeId);
3697
+ }
3698
+ const sourceBlendNodeId = buildNormalizedAdditiveBlendNodeId({
3699
+ nodeIdPrefix: `input_source_blend_${safeInputId}`,
3700
+ sourceNodeIds: sourceBranchNodeIds,
3701
+ baseline: composeBaseline
3702
+ });
3703
+ const overrideEnabledNodeId = `input_override_enabled_${safeInputId}`;
3704
+ nodes.push({
3705
+ id: overrideEnabledNodeId,
3706
+ type: "input",
3707
+ params: {
3708
+ path: stagedConfig.override.enabledPath,
3709
+ value: { float: stagedConfig.override.enabledDefault ? 1 : 0 }
3710
+ }
3711
+ });
3712
+ const overrideValueNodeId = `input_override_value_${safeInputId}`;
3713
+ nodes.push({
3714
+ id: overrideValueNodeId,
3715
+ type: "input",
3716
+ params: {
3717
+ path: stagedConfig.override.valuePath,
3718
+ value: { float: stagedConfig.override.valueDefault }
3719
+ }
3720
+ });
3721
+ const overrideDeltaNodeId = `input_override_delta_${safeInputId}`;
3722
+ nodes.push({
3723
+ id: overrideDeltaNodeId,
3724
+ type: "subtract"
3725
+ });
3726
+ edges.push(
3727
+ {
3728
+ from: { nodeId: overrideValueNodeId },
3729
+ to: { nodeId: overrideDeltaNodeId, portId: "lhs" }
3730
+ },
3731
+ {
3732
+ from: { nodeId: sourceBlendNodeId },
3733
+ to: { nodeId: overrideDeltaNodeId, portId: "rhs" }
3734
+ }
3735
+ );
3736
+ const overrideScaleNodeId = `input_override_scale_${safeInputId}`;
3737
+ nodes.push({
3738
+ id: overrideScaleNodeId,
3739
+ type: "multiply"
3740
+ });
3741
+ edges.push(
3742
+ {
3743
+ from: { nodeId: overrideEnabledNodeId },
3744
+ to: { nodeId: overrideScaleNodeId, portId: "operand_1" }
3745
+ },
3746
+ {
3747
+ from: { nodeId: overrideDeltaNodeId },
3748
+ to: { nodeId: overrideScaleNodeId, portId: "operand_2" }
3749
+ }
3750
+ );
3751
+ const overrideSelectedNodeId = `input_override_selected_${safeInputId}`;
3752
+ nodes.push({
3753
+ id: overrideSelectedNodeId,
3754
+ type: "add"
3755
+ });
3756
+ edges.push(
3757
+ {
3758
+ from: { nodeId: sourceBlendNodeId },
3759
+ to: { nodeId: overrideSelectedNodeId, portId: "operand_1" }
3760
+ },
3761
+ {
3762
+ from: { nodeId: overrideScaleNodeId },
3763
+ to: { nodeId: overrideSelectedNodeId, portId: "operand_2" }
3764
+ }
3765
+ );
3766
+ if (!stagedConfig.clamp.enabled) {
3767
+ return overrideSelectedNodeId;
3768
+ }
3769
+ const minValue = Number.isFinite(input.range.min) ? input.range.min : -1;
3770
+ const maxValue = Number.isFinite(input.range.max) ? input.range.max : 1;
3771
+ const clampNodeId = `input_effective_${safeInputId}`;
3772
+ nodes.push({
3773
+ id: clampNodeId,
3774
+ type: "clamp",
3775
+ inputDefaults: { min: minValue, max: maxValue }
3776
+ });
3777
+ edges.push({
3778
+ from: { nodeId: overrideSelectedNodeId },
3779
+ to: { nodeId: clampNodeId, portId: "in" }
3780
+ });
3781
+ return clampNodeId;
3782
+ };
3053
3783
  const ensureInputNode = (inputId) => {
3054
3784
  const existing = inputNodes.get(inputId);
3055
3785
  if (existing) {
@@ -3061,7 +3791,8 @@ function buildRigGraphSpec({
3061
3791
  }
3062
3792
  const defaultValue = Number.isFinite(input.defaultValue) ? input.defaultValue : 0;
3063
3793
  const inputBindingRaw = inputBindings[inputId];
3064
- if (inputBindingRaw) {
3794
+ const isStagedInput = hasRigPipelineV1InputConfig(pipelineV1, inputId);
3795
+ if (isStagedInput || inputBindingRaw) {
3065
3796
  if (buildingDerived.has(inputId)) {
3066
3797
  const issueSet = bindingIssues.get(inputId) ?? /* @__PURE__ */ new Set();
3067
3798
  issueSet.add("Derived input cycle detected.");
@@ -3070,6 +3801,21 @@ function buildRigGraphSpec({
3070
3801
  }
3071
3802
  buildingDerived.add(inputId);
3072
3803
  try {
3804
+ if (isStagedInput) {
3805
+ const stagedConfig = resolveRigPipelineV1InputConfig({
3806
+ faceId,
3807
+ input,
3808
+ pipelineV1
3809
+ });
3810
+ stagedPipelineByInputId.set(input.id, stagedConfig);
3811
+ computedInputs.add(inputId);
3812
+ const record3 = {
3813
+ nodeId: buildStagedEffectiveInputNodeId(input, stagedConfig),
3814
+ input
3815
+ };
3816
+ inputNodes.set(inputId, record3);
3817
+ return record3;
3818
+ }
3073
3819
  const target = bindingTargetFromInput(input);
3074
3820
  const binding = ensureBindingStructure(inputBindingRaw, target);
3075
3821
  const requiresSelf = binding.inputId === SELF_BINDING_ID2 || binding.slots.some((slot) => slot.inputId === SELF_BINDING_ID2);
@@ -3093,7 +3839,10 @@ function buildRigGraphSpec({
3093
3839
  animatableId: inputId,
3094
3840
  component: void 0,
3095
3841
  safeId: sanitizeNodeId(inputId),
3842
+ enforceRigBoundaryRules: false,
3096
3843
  context: {
3844
+ inputsById,
3845
+ inputBindings,
3097
3846
  nodes,
3098
3847
  edges,
3099
3848
  ensureInputNode,
@@ -3113,12 +3862,18 @@ function buildRigGraphSpec({
3113
3862
  value: input.defaultValue
3114
3863
  }
3115
3864
  });
3116
- const record3 = { nodeId: constNodeId, input };
3865
+ const record3 = {
3866
+ nodeId: buildLegacyEffectiveInputNodeId(input, constNodeId),
3867
+ input
3868
+ };
3117
3869
  inputNodes.set(inputId, record3);
3118
3870
  return record3;
3119
3871
  }
3120
3872
  computedInputs.add(inputId);
3121
- const record2 = { nodeId: valueNodeId, input };
3873
+ const record2 = {
3874
+ nodeId: buildLegacyEffectiveInputNodeId(input, valueNodeId),
3875
+ input
3876
+ };
3122
3877
  inputNodes.set(inputId, record2);
3123
3878
  return record2;
3124
3879
  } finally {
@@ -3134,7 +3889,10 @@ function buildRigGraphSpec({
3134
3889
  value: { float: defaultValue }
3135
3890
  }
3136
3891
  });
3137
- const record = { nodeId, input };
3892
+ const record = {
3893
+ nodeId: buildLegacyEffectiveInputNodeId(input, nodeId),
3894
+ input
3895
+ };
3138
3896
  inputNodes.set(inputId, record);
3139
3897
  return record;
3140
3898
  };
@@ -3175,7 +3933,10 @@ function buildRigGraphSpec({
3175
3933
  animatableId: component.animatableId,
3176
3934
  component: component.component,
3177
3935
  safeId: component.safeId,
3936
+ enforceRigBoundaryRules: true,
3178
3937
  context: {
3938
+ inputsById,
3939
+ inputBindings,
3179
3940
  nodes,
3180
3941
  edges,
3181
3942
  ensureInputNode,
@@ -3323,6 +4084,61 @@ function buildRigGraphSpec({
3323
4084
  const filteredSummaryBindings = summaryBindings.filter(
3324
4085
  (binding) => outputs.has(binding.animatableId) || computedInputs.has(binding.animatableId)
3325
4086
  );
4087
+ const pipelineV1ByInputId = stagedPipelineByInputId.size > 0 ? Object.fromEntries(
4088
+ Array.from(stagedPipelineByInputId.entries()).map(
4089
+ ([inputId, stagedConfig]) => [
4090
+ inputId,
4091
+ {
4092
+ inputId: stagedConfig.inputId,
4093
+ parents: stagedConfig.parents.map((parent) => ({
4094
+ linkId: parent.linkId,
4095
+ inputId: parent.inputId,
4096
+ alias: parent.alias,
4097
+ scale: parent.scale,
4098
+ offset: parent.offset,
4099
+ enabled: parent.enabled,
4100
+ expression: parent.expression
4101
+ })),
4102
+ children: stagedConfig.children.map((child) => ({
4103
+ linkId: child.linkId,
4104
+ childInputId: child.childInputId
4105
+ })),
4106
+ parentBlend: {
4107
+ mode: stagedConfig.parentBlend.mode,
4108
+ expression: stagedConfig.parentBlend.expression
4109
+ },
4110
+ poseSource: {
4111
+ targetIds: [...stagedConfig.poseSource.targetIds]
4112
+ },
4113
+ directInput: {
4114
+ enabled: stagedConfig.directInput.enabled,
4115
+ valuePath: stagedConfig.directInput.valuePath
4116
+ },
4117
+ sourceBlend: {
4118
+ mode: stagedConfig.sourceBlend.mode
4119
+ },
4120
+ sourceFallback: {
4121
+ whenNoSources: stagedConfig.sourceFallback.whenNoSources
4122
+ },
4123
+ clamp: {
4124
+ enabled: stagedConfig.clamp.enabled
4125
+ },
4126
+ override: {
4127
+ enabledDefault: stagedConfig.override.enabledDefault,
4128
+ valueDefault: stagedConfig.override.valueDefault,
4129
+ enabledPath: stagedConfig.override.enabledPath,
4130
+ valuePath: stagedConfig.override.valuePath
4131
+ }
4132
+ }
4133
+ ]
4134
+ )
4135
+ ) : void 0;
4136
+ const hasPipelineLinks = pipelineV1?.links && typeof pipelineV1.links === "object" && Object.keys(pipelineV1.links).length > 0;
4137
+ const pipelineV1Metadata = pipelineV1ByInputId || hasPipelineLinks ? {
4138
+ version: RIG_PIPELINE_V1_VERSION,
4139
+ ...pipelineV1ByInputId ? { byInputId: pipelineV1ByInputId } : {},
4140
+ ...hasPipelineLinks ? { links: cloneJsonLike2(pipelineV1?.links) } : {}
4141
+ } : void 0;
3326
4142
  const vizijMetadata = {
3327
4143
  vizij: {
3328
4144
  faceId,
@@ -3359,7 +4175,8 @@ function buildRigGraphSpec({
3359
4175
  valueType: binding.valueType,
3360
4176
  issues: binding.issues ? [...binding.issues] : void 0,
3361
4177
  metadata: binding.metadata ? cloneJsonLike2(binding.metadata) : void 0
3362
- }))
4178
+ })),
4179
+ ...pipelineV1Metadata ? { pipelineV1: pipelineV1Metadata } : {}
3363
4180
  }
3364
4181
  };
3365
4182
  const issuesByTarget = {};
@@ -3755,6 +4572,9 @@ function normalizeRegistryVariadicSpec(spec) {
3755
4572
  if (typeof spec.max === "number") {
3756
4573
  normalized.max = spec.max;
3757
4574
  }
4575
+ if (spec.keyed) {
4576
+ normalized.keyed = true;
4577
+ }
3758
4578
  return normalized;
3759
4579
  }
3760
4580
  function normalizeRegistryParamSpec(param) {