@babylonjs/loaders 9.26.2 → 9.27.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.
Files changed (33) hide show
  1. package/glTF/2.0/Extensions/EXT_texture_avif.pure.js +1 -1
  2. package/glTF/2.0/Extensions/EXT_texture_avif.pure.js.map +1 -1
  3. package/glTF/2.0/Extensions/EXT_texture_webp.pure.js +1 -1
  4. package/glTF/2.0/Extensions/EXT_texture_webp.pure.js.map +1 -1
  5. package/glTF/2.0/Extensions/KHR_interactivity/declarationMapper.d.ts +33 -0
  6. package/glTF/2.0/Extensions/KHR_interactivity/declarationMapper.js +166 -37
  7. package/glTF/2.0/Extensions/KHR_interactivity/declarationMapper.js.map +1 -1
  8. package/glTF/2.0/Extensions/KHR_interactivity/index.d.ts +1 -0
  9. package/glTF/2.0/Extensions/KHR_interactivity/index.js +1 -0
  10. package/glTF/2.0/Extensions/KHR_interactivity/index.js.map +1 -1
  11. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphExporter.d.ts +243 -0
  12. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphExporter.js +2335 -0
  13. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphExporter.js.map +1 -0
  14. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphModel.d.ts +82 -1
  15. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphModel.js +180 -34
  16. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphModel.js.map +1 -1
  17. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphParser.d.ts +14 -2
  18. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphParser.js +220 -34
  19. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphParser.js.map +1 -1
  20. package/glTF/2.0/Extensions/KHR_interactivity/pure.d.ts +1 -0
  21. package/glTF/2.0/Extensions/KHR_interactivity/pure.js +1 -0
  22. package/glTF/2.0/Extensions/KHR_interactivity/pure.js.map +1 -1
  23. package/glTF/2.0/Extensions/KHR_interactivity.pure.d.ts +4 -0
  24. package/glTF/2.0/Extensions/KHR_interactivity.pure.js +6 -2
  25. package/glTF/2.0/Extensions/KHR_interactivity.pure.js.map +1 -1
  26. package/glTF/2.0/Extensions/KHR_texture_basisu.pure.js +1 -1
  27. package/glTF/2.0/Extensions/KHR_texture_basisu.pure.js.map +1 -1
  28. package/glTF/2.0/glTFLoader.pure.d.ts +2 -1
  29. package/glTF/2.0/glTFLoader.pure.js +41 -4
  30. package/glTF/2.0/glTFLoader.pure.js.map +1 -1
  31. package/glTF/2.0/glTFLoaderInterfaces.d.ts +9 -0
  32. package/glTF/2.0/glTFLoaderInterfaces.js.map +1 -1
  33. package/package.json +3 -3
@@ -0,0 +1,2335 @@
1
+ import { FlowGraphExecutionBlock } from "@babylonjs/core/FlowGraph/flowGraphExecutionBlock.js";
2
+ import { Matrix } from "@babylonjs/core/Maths/math.vector.pure.js";
3
+ import { GetInteractivityOperationRegistry, getMappingForDeclaration, HasDefaultInteractivityFlowInput, NormalizeInteractivityEventDataConfiguration, ParseDebugLogTemplate, } from "./declarationMapper.js";
4
+ import { _CreateKHRInteractivityRuntimeValueSnapshot, _NormalizeKHRInteractivityRuntimeValue, CloneKHRInteractivityGraph, CreateKHRInteractivityGraphModel, gltfTypeToBabylonType, } from "./interactivityGraphModel.js";
5
+ /**
6
+ * Error thrown when export cannot preserve the authored behavior graph.
7
+ */
8
+ export class KHRInteractivityExportError extends Error {
9
+ /**
10
+ * @param diagnostics precise export diagnostics
11
+ */
12
+ constructor(diagnostics) {
13
+ super(diagnostics.map((diagnostic) => `${diagnostic.path}: ${diagnostic.message}`).join("\n"));
14
+ this.diagnostics = diagnostics;
15
+ this.name = "KHRInteractivityExportError";
16
+ }
17
+ }
18
+ const _KnownIndexedRootCollections = new Set(["nodes", "animations", "cameras", "materials", "meshes", "textures", "images", "samplers", "skins", "scenes"]);
19
+ const _UnsupportedBlockClassName = "FlowGraphUnsupportedInteractivityBlock";
20
+ const _CompanionNodeExtensions = ["KHR_node_hoverability", "KHR_node_selectability", "KHR_node_visibility"];
21
+ function _GetPointerExtensionNames(pointer) {
22
+ const segments = pointer.split("/").map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
23
+ const extensions = [];
24
+ for (let index = 0; index < segments.length - 1; index++) {
25
+ if (segments[index] === "extensions" && segments[index + 1]) {
26
+ extensions.push(segments[index + 1]);
27
+ }
28
+ }
29
+ return extensions;
30
+ }
31
+ function _CanPreserveNestedExtensionPointer(pointer, resolvedCollection) {
32
+ const segments = pointer.split("/").map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
33
+ const extensionIndex = segments.indexOf("extensions");
34
+ return (extensionIndex > 1 &&
35
+ (resolvedCollection ?? segments[1]) === "nodes" &&
36
+ _CompanionNodeExtensions.includes(segments[extensionIndex + 1]));
37
+ }
38
+ const _FlowGraphTypeToKHRSignature = {
39
+ ["number" /* FlowGraphTypes.Number */]: "float",
40
+ ["boolean" /* FlowGraphTypes.Boolean */]: "bool",
41
+ ["FlowGraphInteger" /* FlowGraphTypes.Integer */]: "int",
42
+ ["string" /* FlowGraphTypes.String */]: "ref",
43
+ ["Vector2" /* FlowGraphTypes.Vector2 */]: "float2",
44
+ ["Vector3" /* FlowGraphTypes.Vector3 */]: "float3",
45
+ ["Vector4" /* FlowGraphTypes.Vector4 */]: "float4",
46
+ ["Quaternion" /* FlowGraphTypes.Quaternion */]: "float4",
47
+ ["Color3" /* FlowGraphTypes.Color3 */]: "float3",
48
+ ["Color4" /* FlowGraphTypes.Color4 */]: "float4",
49
+ ["Matrix2D" /* FlowGraphTypes.Matrix2D */]: "float2x2",
50
+ ["Matrix3D" /* FlowGraphTypes.Matrix3D */]: "float3x3",
51
+ ["Matrix" /* FlowGraphTypes.Matrix */]: "float4x4",
52
+ Mesh: "ref",
53
+ TransformNode: "ref",
54
+ Camera: "ref",
55
+ Light: "ref",
56
+ Material: "ref",
57
+ AnimationGroup: "ref",
58
+ };
59
+ function _CloneJson(value) {
60
+ if (Array.isArray(value)) {
61
+ return value.map((entry) => _CloneJson(entry));
62
+ }
63
+ if (value !== null && typeof value === "object") {
64
+ const clone = {};
65
+ for (const [key, entry] of Object.entries(value)) {
66
+ Object.defineProperty(clone, key, {
67
+ configurable: true,
68
+ enumerable: true,
69
+ value: _CloneJson(entry),
70
+ writable: true,
71
+ });
72
+ }
73
+ return clone;
74
+ }
75
+ return value;
76
+ }
77
+ function _GetBlockProvenance(block) {
78
+ return block.metadata?.khrInteractivity;
79
+ }
80
+ function _GetSocketProvenance(connection) {
81
+ return connection.metadata?.khrInteractivity;
82
+ }
83
+ function _GetOwn(dictionary, key) {
84
+ return dictionary && Object.prototype.hasOwnProperty.call(dictionary, key) ? dictionary[key] : undefined;
85
+ }
86
+ function _FullOperationName(op, extension) {
87
+ return extension ? `${op}:${extension}` : op;
88
+ }
89
+ function _RuntimeBlockClassName(className) {
90
+ return className.substring(className.lastIndexOf("/") + 1);
91
+ }
92
+ function _GetRoleBlock(blocks, role) {
93
+ return blocks.find((block) => _GetBlockProvenance(block)?.role === role);
94
+ }
95
+ function _FindDataInput(blocks, nodeIndex, socket) {
96
+ for (const block of blocks) {
97
+ for (const input of block.dataInputs) {
98
+ const provenance = _GetSocketProvenance(input);
99
+ if (provenance?.nodeIndex === nodeIndex && provenance.kind === "value" && provenance.direction === "input" && provenance.socket === socket) {
100
+ return input;
101
+ }
102
+ }
103
+ }
104
+ return undefined;
105
+ }
106
+ function _NormalizeValue(value) {
107
+ return _NormalizeKHRInteractivityRuntimeValue(value);
108
+ }
109
+ function _CreateTypedRuntimeValueSnapshot(graph, type, value) {
110
+ const signature = type === undefined ? undefined : graph.types?.[type]?.signature;
111
+ const components = Array.isArray(value) ? value : _NormalizeValue(value);
112
+ return _CreateKHRInteractivityRuntimeValueSnapshot(signature === "float4x4" && components ? Matrix.FromArray(components) : components);
113
+ }
114
+ function _CreateEventSchemaRuntimeSnapshot(value) {
115
+ const runtimeSchema = Array.isArray(value)
116
+ ? value.map((entry) => {
117
+ if (entry === null || typeof entry !== "object") {
118
+ return entry;
119
+ }
120
+ const definition = entry;
121
+ const runtimeValue = definition.type === "Matrix" /* FlowGraphTypes.Matrix */ && Array.isArray(definition.value)
122
+ ? Array.from(Matrix.FromArray(definition.value).asArray())
123
+ : definition.value;
124
+ return definition.value === undefined ? entry : { ...definition, value: runtimeValue };
125
+ })
126
+ : value;
127
+ return _CreateKHRInteractivityRuntimeValueSnapshot(runtimeSchema);
128
+ }
129
+ function _ValuesEqual(left, right) {
130
+ if (!left || !right || left.length !== right.length) {
131
+ return left === right;
132
+ }
133
+ for (let index = 0; index < left.length; index++) {
134
+ if (typeof left[index] === "number" && typeof right[index] === "number" && Number.isNaN(left[index]) && Number.isNaN(right[index])) {
135
+ continue;
136
+ }
137
+ if (left[index] !== right[index]) {
138
+ return false;
139
+ }
140
+ }
141
+ return true;
142
+ }
143
+ function _JsonEquivalent(left, right) {
144
+ if (typeof left === "number" && typeof right === "number" && Number.isNaN(left) && Number.isNaN(right)) {
145
+ return true;
146
+ }
147
+ if (left === right) {
148
+ return true;
149
+ }
150
+ if (Array.isArray(left) && Array.isArray(right)) {
151
+ return left.length === right.length && left.every((entry, index) => _JsonEquivalent(entry, right[index]));
152
+ }
153
+ if (left !== null && right !== null && typeof left === "object" && typeof right === "object") {
154
+ const leftEntries = Object.entries(left);
155
+ const rightKeys = Object.keys(right);
156
+ return (leftEntries.length === rightKeys.length &&
157
+ leftEntries.every(([key, value]) => Object.prototype.hasOwnProperty.call(right, key) && _JsonEquivalent(value, right[key])));
158
+ }
159
+ return false;
160
+ }
161
+ function _GetTypeDefault(signature) {
162
+ switch (signature) {
163
+ case "bool":
164
+ return [false];
165
+ case "int":
166
+ return [0];
167
+ case "ref":
168
+ return [""];
169
+ case "float":
170
+ return [NaN];
171
+ case "float2":
172
+ return [NaN, NaN];
173
+ case "float3":
174
+ return [NaN, NaN, NaN];
175
+ case "float4":
176
+ case "float2x2":
177
+ return [NaN, NaN, NaN, NaN];
178
+ case "float3x3":
179
+ return [NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN];
180
+ case "float4x4":
181
+ return [NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN];
182
+ default:
183
+ return [];
184
+ }
185
+ }
186
+ function _GetOrAddInputTypeIndex(graph, input, mapping, logicalNode, socket) {
187
+ if (logicalNode.operation === "variable/set" && /^(0|[1-9]\d*)$/.test(socket)) {
188
+ return graph.variables?.[parseInt(socket, 10)]?.type;
189
+ }
190
+ if (logicalNode.operation === "event/send") {
191
+ const eventProperty = logicalNode.mapping?.configuration?.event;
192
+ const eventIndex = eventProperty ? _GetMappedConfigurationValue(logicalNode, "event", eventProperty, graph)?.[0] : undefined;
193
+ if (typeof eventIndex === "number") {
194
+ return graph.events?.[eventIndex]?.values?.[socket]?.type;
195
+ }
196
+ }
197
+ if (logicalNode.operation === "pointer/get" || logicalNode.operation === "pointer/set" || logicalNode.operation === "pointer/interpolate") {
198
+ const pointerProperty = logicalNode.mapping?.configuration?.pointer;
199
+ const pointer = pointerProperty ? _GetMappedConfigurationValue(logicalNode, "pointer", pointerProperty, graph)?.[0] : undefined;
200
+ if (typeof pointer === "string") {
201
+ const segment = pointer.split("/").find((entry) => entry === `[${socket}]` || entry === `{${socket}}`);
202
+ const semanticSignature = segment?.startsWith("[") ? "int" : segment?.startsWith("{") ? "ref" : undefined;
203
+ if (semanticSignature) {
204
+ const existing = graph.types?.findIndex((type) => type.signature === semanticSignature) ?? -1;
205
+ if (existing >= 0) {
206
+ return existing;
207
+ }
208
+ graph.types ?? (graph.types = []);
209
+ graph.types.push({ signature: semanticSignature });
210
+ return graph.types.length - 1;
211
+ }
212
+ }
213
+ }
214
+ const mappedType = mapping?.gltfType;
215
+ const signature = mappedType === "number"
216
+ ? "float"
217
+ : mappedType === "boolean"
218
+ ? "bool"
219
+ : mappedType === "vector2"
220
+ ? "float2"
221
+ : mappedType === "vector3"
222
+ ? "float3"
223
+ : mappedType === "vector4"
224
+ ? "float4"
225
+ : mappedType && mappedType !== "any"
226
+ ? mappedType
227
+ : _FlowGraphTypeToKHRSignature[input.richType.typeName];
228
+ if (!signature || !gltfTypeToBabylonType[signature]) {
229
+ return undefined;
230
+ }
231
+ graph.types ?? (graph.types = []);
232
+ const existing = graph.types.findIndex((type) => type.signature === signature);
233
+ if (existing >= 0) {
234
+ return existing;
235
+ }
236
+ graph.types.push({ signature: signature });
237
+ return graph.types.length - 1;
238
+ }
239
+ function _GetOrAddVariableTypeIndex(graph, flowGraphType) {
240
+ const signature = _FlowGraphTypeToKHRSignature[flowGraphType];
241
+ if (!signature || !gltfTypeToBabylonType[signature]) {
242
+ return undefined;
243
+ }
244
+ graph.types ?? (graph.types = []);
245
+ const existing = graph.types.findIndex((type) => type.signature === signature);
246
+ if (existing >= 0) {
247
+ return existing;
248
+ }
249
+ graph.types.push({ signature: signature });
250
+ return graph.types.length - 1;
251
+ }
252
+ function _GetSourceValue(graph, value, mapping, targetFps = 60) {
253
+ const signature = graph.types?.[value.type]?.signature ?? "custom";
254
+ const source = value.value?.slice() ?? _GetTypeDefault(signature);
255
+ if (mapping?.convertConnectedTimeToFrames && source.length === 1 && typeof source[0] === "number") {
256
+ source[0] *= targetFps;
257
+ }
258
+ return source;
259
+ }
260
+ function _GetConfigurationBlock(logicalNode, property) {
261
+ const targetBlock = property.toBlock;
262
+ if (targetBlock) {
263
+ return logicalNode.blocks.find((block) => block.getClassName() === _RuntimeBlockClassName(targetBlock) || _GetBlockProvenance(block)?.role === logicalNode.mapping?.blocks.indexOf(targetBlock));
264
+ }
265
+ return _GetRoleBlock(logicalNode.blocks, 0);
266
+ }
267
+ function _GetMappedConfigurationValue(logicalNode, key, property, sourceGraph, preserveCanonicalWhenUnchanged = true) {
268
+ const block = _GetConfigurationBlock(logicalNode, property);
269
+ if (!block) {
270
+ return undefined;
271
+ }
272
+ const value = block.config?.[property.name];
273
+ const provenance = _GetConfigurationProvenance(logicalNode, key, property);
274
+ if (preserveCanonicalWhenUnchanged && provenance && _ValuesEqual(_NormalizeValue(value), _NormalizeValue(provenance.runtimeValue))) {
275
+ return provenance.sourceValue ? _CloneJson(provenance.sourceValue) : undefined;
276
+ }
277
+ if (property.indexSource === "variables") {
278
+ const names = (Array.isArray(value) ? value : [value]).filter((entry) => typeof entry === "string");
279
+ const indices = names.map((name) => (/^staticVariable_(0|[1-9]\d*)$/.test(name) ? parseInt(name.substring("staticVariable_".length), 10) : -1));
280
+ return indices.every((index) => index >= 0 && index < (sourceGraph.variables?.length ?? 0)) ? indices : undefined;
281
+ }
282
+ if (property.indexSource === "events") {
283
+ const eventId = value;
284
+ const eventIndex = sourceGraph.events?.findIndex((event) => event.id === eventId);
285
+ return eventIndex !== undefined && eventIndex >= 0 ? [eventIndex] : undefined;
286
+ }
287
+ if (property.indexSource === "assetNodes" && typeof value === "string") {
288
+ return undefined;
289
+ }
290
+ if (key === "useSlerp") {
291
+ return [value === "Quaternion"];
292
+ }
293
+ return _NormalizeValue(value);
294
+ }
295
+ function _GetConfigurationProvenance(logicalNode, key, property) {
296
+ const block = _GetConfigurationBlock(logicalNode, property);
297
+ return block ? _GetOwn(_GetBlockProvenance(block)?.configuration, key) : undefined;
298
+ }
299
+ function _IsValidConfigurationValue(value, property, graph, assetNodeCount) {
300
+ if (!value) {
301
+ return false;
302
+ }
303
+ const isInt = (entry) => typeof entry === "number" && Number.isInteger(entry) && entry >= -2147483648 && entry <= 2147483647;
304
+ let valid;
305
+ switch (property.configurationType) {
306
+ case "bool":
307
+ valid = value.length === 1 && typeof value[0] === "boolean";
308
+ break;
309
+ case "int":
310
+ valid = value.length === 1 && isInt(value[0]);
311
+ break;
312
+ case "int[]":
313
+ valid = value.every(isInt);
314
+ break;
315
+ case "string":
316
+ valid = value.length === 1 && typeof value[0] === "string";
317
+ break;
318
+ default:
319
+ valid = value.length > 0;
320
+ break;
321
+ }
322
+ if (!valid || (property.minItems !== undefined && value.length < property.minItems)) {
323
+ return false;
324
+ }
325
+ if (property.minimum !== undefined && value.some((entry) => typeof entry !== "number" || entry < property.minimum)) {
326
+ return false;
327
+ }
328
+ if (property.maximum !== undefined && value.some((entry) => typeof entry !== "number" || entry > property.maximum)) {
329
+ return false;
330
+ }
331
+ if (property.indexSource) {
332
+ const lengths = {
333
+ types: graph.types?.length ?? 0,
334
+ variables: graph.variables?.length ?? 0,
335
+ events: graph.events?.length ?? 0,
336
+ nodes: graph.nodes?.length ?? 0,
337
+ assetNodes: assetNodeCount ?? 0,
338
+ };
339
+ if (value.some((entry) => !isInt(entry) || entry < 0 || entry >= lengths[property.indexSource])) {
340
+ return false;
341
+ }
342
+ }
343
+ if (property.allowedSignatures) {
344
+ const typeIndex = value[0];
345
+ const signature = typeof typeIndex === "number" ? graph.types?.[typeIndex]?.signature : undefined;
346
+ if (!signature || !property.allowedSignatures.includes(signature)) {
347
+ return false;
348
+ }
349
+ }
350
+ if (property.allowedValues && value.some((entry) => !property.allowedValues.includes(entry))) {
351
+ return false;
352
+ }
353
+ if (property.debugLogTemplate && (typeof value[0] !== "string" || !ParseDebugLogTemplate(value[0]).valid)) {
354
+ return false;
355
+ }
356
+ return true;
357
+ }
358
+ function _GetEffectiveConfigurationValue(node, mapping, key, property, graph, logicalNode, assetNodeCount) {
359
+ const getCandidate = (candidateKey, candidateProperty) => logicalNode ? _GetMappedConfigurationValue(logicalNode, candidateKey, candidateProperty, graph) : node.configuration?.[candidateKey]?.value;
360
+ if (property.configurationGroup) {
361
+ for (const [candidateKey, candidateProperty] of Object.entries(mapping.configuration ?? {})) {
362
+ if (candidateProperty.configurationGroup === property.configurationGroup &&
363
+ !_IsValidConfigurationValue(getCandidate(candidateKey, candidateProperty), candidateProperty, graph, assetNodeCount)) {
364
+ return property.defaultValue === undefined ? undefined : _NormalizeValue(property.defaultValue);
365
+ }
366
+ }
367
+ }
368
+ const candidate = getCandidate(key, property);
369
+ if (!_IsValidConfigurationValue(candidate, property, graph, assetNodeCount)) {
370
+ return property.defaultValue === undefined ? undefined : _NormalizeValue(property.defaultValue);
371
+ }
372
+ return property.uniqueValues ? Array.from(new Set(candidate)) : candidate;
373
+ }
374
+ function _PushDiagnostic(diagnostics, diagnostic) {
375
+ diagnostics.push({ severity: "error", ...diagnostic });
376
+ }
377
+ function _SetOwnProperty(target, key, value) {
378
+ Object.defineProperty(target, key, {
379
+ configurable: true,
380
+ enumerable: true,
381
+ value,
382
+ writable: true,
383
+ });
384
+ }
385
+ /**
386
+ * Detached representability and export plan for one or more FlowGraphs.
387
+ */
388
+ export class KHRInteractivityExportPlan {
389
+ /** Additional operation/companion extensions referenced by the exported graphs. */
390
+ get additionalExtensionsUsed() {
391
+ return this._additionalExtensionsUsed;
392
+ }
393
+ /**
394
+ * @param _flowGraphs FlowGraphs to analyze without mutating them
395
+ * @param _options canonical source and export settings
396
+ */
397
+ constructor(_flowGraphs, _options = {}) {
398
+ this._flowGraphs = _flowGraphs;
399
+ this._options = _options;
400
+ this._graphAnalyses = [];
401
+ this._additionalExtensionsUsed = [];
402
+ this._rootDiagnostics = [];
403
+ this._isPreflight = false;
404
+ this.required = _options.required ?? true;
405
+ this.additionalExtensionsRequired = Array.from(new Set(_options.additionalExtensionsRequired ?? [])).sort();
406
+ this._refreshAnalysis();
407
+ }
408
+ _refreshAnalysis() {
409
+ this._rootDiagnostics.length = 0;
410
+ this._graphAnalyses = this._analyzeGraphs();
411
+ this._additionalExtensionsUsed = Array.from(new Set([...this._collectAdditionalExtensions(), ...this.additionalExtensionsRequired])).sort();
412
+ const unscopedBuildDiagnostics = this._mergeBuildDiagnostics(this._validateBuildWithSourceIndices());
413
+ const nodes = this._graphAnalyses.flatMap((graph) => graph.nodes.map((node) => ({
414
+ graphIndex: graph.graphIndex,
415
+ nodeIndex: node.sourceIndex,
416
+ operation: node.operation,
417
+ blockIds: node.blocks.map((block) => block.uniqueId).sort(),
418
+ classification: node.classification,
419
+ diagnostics: node.diagnostics.slice(),
420
+ })));
421
+ const diagnostics = this._sortDiagnostics(this._rootDiagnostics.concat(this._graphAnalyses.flatMap((graph) => graph.diagnostics.concat(graph.nodes.flatMap((node) => node.diagnostics))), unscopedBuildDiagnostics));
422
+ this._analysis = {
423
+ representable: !diagnostics.some((diagnostic) => diagnostic.severity === "error"),
424
+ nodes,
425
+ diagnostics,
426
+ };
427
+ }
428
+ _mergeBuildDiagnostics(diagnostics) {
429
+ const unscoped = [];
430
+ for (const diagnostic of diagnostics) {
431
+ const pathNodeIndex = /\/nodes\/(\d+)(?:\/|$)/.exec(diagnostic.path)?.[1];
432
+ const nodeIndex = diagnostic.nodeIndex ?? (pathNodeIndex === undefined ? undefined : parseInt(pathNodeIndex, 10));
433
+ const graph = diagnostic.graphIndex === undefined ? undefined : this._graphAnalyses[diagnostic.graphIndex];
434
+ const node = nodeIndex === undefined ? undefined : graph?.nodes.find((candidate) => candidate.sourceIndex === nodeIndex);
435
+ if (!node) {
436
+ unscoped.push(diagnostic);
437
+ continue;
438
+ }
439
+ if (!node.diagnostics.some((existing) => existing.code === diagnostic.code && existing.path === diagnostic.path && existing.message === diagnostic.message)) {
440
+ node.diagnostics.push({ ...diagnostic, nodeIndex });
441
+ }
442
+ if (diagnostic.severity === "error" && (node.classification === "exact" || node.classification === "inverse-composite")) {
443
+ node.classification = "lossy";
444
+ }
445
+ }
446
+ return unscoped;
447
+ }
448
+ /**
449
+ * Gets the detached representability analysis.
450
+ * @returns current analysis
451
+ */
452
+ analyze() {
453
+ this._refreshAnalysis();
454
+ return {
455
+ representable: this._analysis.representable,
456
+ nodes: this._analysis.nodes.map((node) => ({ ...node, blockIds: node.blockIds.slice(), diagnostics: node.diagnostics.slice() })),
457
+ diagnostics: this._analysis.diagnostics.slice(),
458
+ };
459
+ }
460
+ /**
461
+ * Builds the extension after final glTF entity remapping is available.
462
+ * @param context serializer remapping context
463
+ * @returns canonical KHR_interactivity payload
464
+ */
465
+ build(context) {
466
+ this._refreshAnalysis();
467
+ if (!this._analysis.representable) {
468
+ throw new KHRInteractivityExportError(this._analysis.diagnostics);
469
+ }
470
+ const diagnostics = [];
471
+ const graphs = this._graphAnalyses.map((analysis) => this._buildGraph(analysis, context, diagnostics));
472
+ this._writeCompanionNodeExtensions(context, diagnostics);
473
+ if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
474
+ throw new KHRInteractivityExportError(this._sortDiagnostics(diagnostics));
475
+ }
476
+ const source = this._options.document?.source ? _CloneJson(this._options.document.source) : { graphs: [] };
477
+ source.graphs = graphs;
478
+ const defaultGraphIndex = this._options.defaultGraphIndex ?? this._options.document?.defaultGraphIndex ?? 0;
479
+ if (defaultGraphIndex < 0 || defaultGraphIndex >= graphs.length) {
480
+ _PushDiagnostic(diagnostics, {
481
+ code: "GRAPH_COUNT_MISMATCH",
482
+ path: "/extensions/KHR_interactivity/graph",
483
+ message: `Default graph index ${defaultGraphIndex} is outside the exported graph range.`,
484
+ });
485
+ throw new KHRInteractivityExportError(diagnostics);
486
+ }
487
+ if (defaultGraphIndex !== 0 || source.graph !== undefined) {
488
+ source.graph = defaultGraphIndex;
489
+ }
490
+ return source;
491
+ }
492
+ _getSourceGraph(graph, graphIndex) {
493
+ const documentGraph = this._options.document?.graphs[graphIndex]?.source;
494
+ if (documentGraph) {
495
+ return CloneKHRInteractivityGraph(documentGraph);
496
+ }
497
+ const provenance = graph.metadata?.khrInteractivity;
498
+ return provenance?.source ? CloneKHRInteractivityGraph(provenance.source) : undefined;
499
+ }
500
+ _validateBuildWithSourceIndices() {
501
+ const diagnostics = [];
502
+ const sourceGLTF = this._options.sourceGLTF;
503
+ const indexOrUndefined = (index) => (index !== undefined && index >= 0 ? index : undefined);
504
+ const context = {
505
+ getNodeCount: () => sourceGLTF?.nodes?.length ?? 0,
506
+ getNodeIndex: (node) => indexOrUndefined(sourceGLTF?.nodes?.findIndex((candidate) => candidate._babylonTransformNode === node)),
507
+ getAnimationIndex: (animation) => indexOrUndefined(sourceGLTF?.animations?.findIndex((candidate) => candidate._babylonAnimationGroup === animation)),
508
+ getCameraIndex: (camera) => indexOrUndefined(sourceGLTF?.cameras?.findIndex((candidate) => candidate._babylonCamera === camera)),
509
+ getMaterialIndex: (material) => indexOrUndefined(sourceGLTF?.materials?.findIndex((candidate) => Object.values(candidate._data ?? {}).some((entry) => entry.babylonMaterial === material))),
510
+ getRootIndex: (collection, entity) => this._getSourceRootIndex(collection, entity),
511
+ setNodeExtension: () => { },
512
+ };
513
+ this._isPreflight = true;
514
+ try {
515
+ for (const analysis of this._graphAnalyses) {
516
+ this._buildGraph(analysis, context, diagnostics);
517
+ }
518
+ this._writeCompanionNodeExtensions(context, diagnostics);
519
+ }
520
+ finally {
521
+ this._isPreflight = false;
522
+ }
523
+ return diagnostics;
524
+ }
525
+ _analyzeGraphs() {
526
+ const analyses = [];
527
+ if (!this._options.document) {
528
+ _PushDiagnostic(this._rootDiagnostics, {
529
+ code: "GRAPH_SOURCE_MISSING",
530
+ path: "/extensions/KHR_interactivity",
531
+ message: "A canonical KHR_interactivity document is required to preserve root metadata and the default graph selection.",
532
+ });
533
+ }
534
+ if (this._flowGraphs.length === 0) {
535
+ _PushDiagnostic(this._rootDiagnostics, {
536
+ code: "GRAPH_COUNT_MISMATCH",
537
+ path: "/extensions/KHR_interactivity/graphs",
538
+ message: "At least one FlowGraph is required for KHR_interactivity export.",
539
+ });
540
+ return analyses;
541
+ }
542
+ for (const diagnostic of this._options.document?.diagnostics ?? []) {
543
+ _PushDiagnostic(this._rootDiagnostics, {
544
+ code: "GRAPH_INVALID",
545
+ path: diagnostic.path,
546
+ message: diagnostic.message,
547
+ severity: diagnostic.severity,
548
+ });
549
+ }
550
+ if (this._options.document && this._options.document.graphs.length !== this._flowGraphs.length) {
551
+ _PushDiagnostic(this._rootDiagnostics, {
552
+ code: "GRAPH_COUNT_MISMATCH",
553
+ path: "/extensions/KHR_interactivity/graphs",
554
+ message: `Canonical source has ${this._options.document.graphs.length} graph(s), but ${this._flowGraphs.length} FlowGraph(s) were supplied.`,
555
+ });
556
+ return analyses;
557
+ }
558
+ for (let graphIndex = 0; graphIndex < this._flowGraphs.length; graphIndex++) {
559
+ const graph = this._flowGraphs[graphIndex];
560
+ const source = this._getSourceGraph(graph, graphIndex);
561
+ const diagnostics = [];
562
+ for (const diagnostic of this._options.document?.graphs[graphIndex]?.diagnostics ?? []) {
563
+ _PushDiagnostic(diagnostics, {
564
+ code: "GRAPH_INVALID",
565
+ graphIndex,
566
+ path: diagnostic.path,
567
+ message: diagnostic.message,
568
+ severity: diagnostic.severity,
569
+ });
570
+ }
571
+ if (!source) {
572
+ _PushDiagnostic(diagnostics, {
573
+ code: "GRAPH_SOURCE_MISSING",
574
+ graphIndex,
575
+ path: `/graphs/${graphIndex}`,
576
+ message: "This FlowGraph has no canonical KHR_interactivity source. Only imported graphs and registered standalone inverse mappings can be exported.",
577
+ });
578
+ const unsupportedNodes = graph.getAllBlocks().map((block) => ({
579
+ sourceIndex: -1,
580
+ declarationIndex: -1,
581
+ operation: "",
582
+ blocks: [block],
583
+ classification: this._classifyStandaloneBlock(block, diagnostics),
584
+ diagnostics: diagnostics.filter((diagnostic) => diagnostic.blockId === block.uniqueId),
585
+ }));
586
+ analyses.push({ graph, graphIndex, source: {}, nodes: unsupportedNodes, diagnostics });
587
+ continue;
588
+ }
589
+ analyses.push({
590
+ graph,
591
+ graphIndex,
592
+ source,
593
+ nodes: this._analyzeImportedNodes(graph, graphIndex, source, diagnostics),
594
+ diagnostics,
595
+ });
596
+ }
597
+ return analyses;
598
+ }
599
+ _classifyStandaloneBlock(block, diagnostics) {
600
+ const candidates = GetInteractivityOperationRegistry().filter((entry) => entry.mapping.blocks.length === 1 && _RuntimeBlockClassName(entry.mapping.blocks[0]) === block.getClassName());
601
+ const path = `/blocks/${block.uniqueId}`;
602
+ if (candidates.length === 0) {
603
+ _PushDiagnostic(diagnostics, {
604
+ code: "BLOCK_UNSUPPORTED",
605
+ path,
606
+ blockId: block.uniqueId,
607
+ message: `FlowGraph block "${block.getClassName()}" has no KHR_interactivity inverse mapping.`,
608
+ });
609
+ return "unsupported";
610
+ }
611
+ _PushDiagnostic(diagnostics, {
612
+ code: "BLOCK_AMBIGUOUS",
613
+ path,
614
+ blockId: block.uniqueId,
615
+ message: candidates.length === 1
616
+ ? `FlowGraph block "${block.getClassName()}" is not associated with canonical KHR source provenance.`
617
+ : `FlowGraph block "${block.getClassName()}" is ambiguous between ${candidates.map((entry) => _FullOperationName(entry.op, entry.extension)).join(", ")}.`,
618
+ });
619
+ return "lossy";
620
+ }
621
+ _analyzeImportedNodes(graph, graphIndex, source, graphDiagnostics) {
622
+ const groups = new Map();
623
+ for (const block of graph.getAllBlocks()) {
624
+ const provenance = _GetBlockProvenance(block);
625
+ if (!provenance || provenance.graphIndex !== graphIndex || provenance.nodeIndex < 0) {
626
+ const diagnostics = [];
627
+ this._classifyStandaloneBlock(block, diagnostics);
628
+ graphDiagnostics.push(...diagnostics);
629
+ groups.set(Number.MIN_SAFE_INTEGER + groups.size, [block]);
630
+ continue;
631
+ }
632
+ const group = groups.get(provenance.nodeIndex) ?? [];
633
+ group.push(block);
634
+ groups.set(provenance.nodeIndex, group);
635
+ }
636
+ const logicalNodes = [];
637
+ for (let nodeIndex = 0; nodeIndex < (source.nodes?.length ?? 0); nodeIndex++) {
638
+ const sourceNode = source.nodes[nodeIndex];
639
+ const declaration = source.declarations?.[sourceNode.declaration];
640
+ const declarationModel = this._options.document?.graphs[graphIndex]?.declarations[sourceNode.declaration];
641
+ const operation = declaration ? _FullOperationName(declaration.op, declaration.extension) : "";
642
+ const mapping = declaration && declarationModel?.support !== "unsupported-extension" ? getMappingForDeclaration(declaration, false) : undefined;
643
+ const blocks = groups.get(nodeIndex) ?? [];
644
+ const diagnostics = [];
645
+ let classification = mapping && mapping.blocks.length > 1 ? "inverse-composite" : "exact";
646
+ if (!declaration) {
647
+ classification = "unsupported";
648
+ _PushDiagnostic(diagnostics, {
649
+ code: "NODE_SOURCE_MISSING",
650
+ graphIndex,
651
+ nodeIndex,
652
+ path: `/graphs/${graphIndex}/nodes/${nodeIndex}/declaration`,
653
+ message: `Declaration index ${sourceNode.declaration} is unavailable.`,
654
+ });
655
+ }
656
+ else if (declaration.extension === "BABYLON") {
657
+ classification = "unsupported";
658
+ _PushDiagnostic(diagnostics, {
659
+ code: "BLOCK_UNSUPPORTED",
660
+ graphIndex,
661
+ nodeIndex,
662
+ path: `/graphs/${graphIndex}/nodes/${nodeIndex}`,
663
+ message: `Compatibility-only operation "${operation}" is not part of ratified KHR_interactivity and cannot be exported as standards-compliant data.`,
664
+ });
665
+ }
666
+ else if (!mapping && !declaration.extension) {
667
+ classification = "unsupported";
668
+ _PushDiagnostic(diagnostics, {
669
+ code: "BLOCK_UNSUPPORTED",
670
+ graphIndex,
671
+ nodeIndex,
672
+ path: `/graphs/${graphIndex}/nodes/${nodeIndex}`,
673
+ message: `Unknown core operation "${declaration.op}" cannot be exported.`,
674
+ });
675
+ }
676
+ else if (!mapping && declaration.extension) {
677
+ if (blocks.length !== 1 || blocks[0].getClassName() !== _UnsupportedBlockClassName) {
678
+ classification = "unsupported";
679
+ _PushDiagnostic(diagnostics, {
680
+ code: "BLOCK_TYPE_MISMATCH",
681
+ graphIndex,
682
+ nodeIndex,
683
+ path: `/graphs/${graphIndex}/nodes/${nodeIndex}`,
684
+ message: `Unsupported extension operation "${operation}" must remain an intact typed no-op block.`,
685
+ });
686
+ }
687
+ else {
688
+ this._validateUnsupportedExtensionBlock(source, sourceNode, nodeIndex, operation, declaration, blocks[0], graphIndex, diagnostics);
689
+ if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
690
+ classification = "lossy";
691
+ }
692
+ }
693
+ }
694
+ else {
695
+ this._validateMappedBlocks(graphIndex, nodeIndex, operation, mapping, blocks, diagnostics);
696
+ if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
697
+ classification = "lossy";
698
+ }
699
+ }
700
+ if (blocks.length === 0) {
701
+ classification = "unsupported";
702
+ _PushDiagnostic(diagnostics, {
703
+ code: "BLOCK_ROLE_MISSING",
704
+ graphIndex,
705
+ nodeIndex,
706
+ path: `/graphs/${graphIndex}/nodes/${nodeIndex}`,
707
+ message: `The FlowGraph blocks for "${operation}" were removed.`,
708
+ });
709
+ }
710
+ groups.delete(nodeIndex);
711
+ logicalNodes.push({
712
+ sourceIndex: nodeIndex,
713
+ declarationIndex: sourceNode.declaration,
714
+ operation,
715
+ mapping,
716
+ blocks,
717
+ classification,
718
+ diagnostics,
719
+ });
720
+ }
721
+ for (const blocks of groups.values()) {
722
+ const block = blocks[0];
723
+ const diagnostics = [];
724
+ const classification = this._classifyStandaloneBlock(block, diagnostics);
725
+ graphDiagnostics.push(...diagnostics);
726
+ logicalNodes.push({
727
+ sourceIndex: -1,
728
+ declarationIndex: -1,
729
+ operation: "",
730
+ blocks,
731
+ classification,
732
+ diagnostics,
733
+ });
734
+ }
735
+ return logicalNodes;
736
+ }
737
+ _validateUnsupportedExtensionBlock(graph, node, nodeIndex, operation, declaration, block, graphIndex, diagnostics) {
738
+ const config = block.config;
739
+ const normalizeValueSockets = (sockets) => Array.isArray(sockets)
740
+ ? sockets
741
+ .map((socket) => socket !== null && typeof socket === "object"
742
+ ? { name: socket.name, signature: socket.signature }
743
+ : socket)
744
+ .sort((left, right) => String(JSON.stringify(left)).localeCompare(String(JSON.stringify(right))))
745
+ : sockets;
746
+ const normalizeFlowSockets = (sockets) => (Array.isArray(sockets) ? sockets.slice().sort() : sockets);
747
+ const expectedValueSockets = (sockets) => Object.entries(sockets ?? {})
748
+ .map(([name, socket]) => ({ name, signature: graph.types?.[socket.type]?.signature }))
749
+ .sort((left, right) => left.name.localeCompare(right.name));
750
+ const expectedInputFlows = Array.from(new Set((graph.nodes ?? []).flatMap((sourceNode) => Object.values(sourceNode.flows ?? {})
751
+ .filter((flow) => flow.node === nodeIndex)
752
+ .map((flow) => flow.socket ?? "in")))).sort();
753
+ const expectedOutputFlows = Object.keys(node.flows ?? {}).sort();
754
+ const actualConnectionNames = {
755
+ inputValues: block.dataInputs.map((connection) => connection.name).sort(),
756
+ outputValues: block.dataOutputs.map((connection) => connection.name).sort(),
757
+ inputFlows: block instanceof FlowGraphExecutionBlock ? block.signalInputs.map((connection) => connection.name).sort() : [],
758
+ outputFlows: block instanceof FlowGraphExecutionBlock ? block.signalOutputs.map((connection) => connection.name).sort() : [],
759
+ };
760
+ const expectedConnectionNames = {
761
+ inputValues: Object.keys(declaration.inputValueSockets ?? {}).sort(),
762
+ outputValues: Object.keys(declaration.outputValueSockets ?? {}).sort(),
763
+ inputFlows: expectedInputFlows,
764
+ outputFlows: expectedOutputFlows,
765
+ };
766
+ if (config.operation !== operation ||
767
+ !_JsonEquivalent(normalizeValueSockets(config.inputValueSockets), expectedValueSockets(declaration.inputValueSockets)) ||
768
+ !_JsonEquivalent(normalizeValueSockets(config.outputValueSockets), expectedValueSockets(declaration.outputValueSockets)) ||
769
+ !_JsonEquivalent(normalizeFlowSockets(config.inputFlowSockets), expectedInputFlows) ||
770
+ !_JsonEquivalent(normalizeFlowSockets(config.outputFlowSockets), expectedOutputFlows) ||
771
+ !_JsonEquivalent(actualConnectionNames, expectedConnectionNames)) {
772
+ _PushDiagnostic(diagnostics, {
773
+ code: "BLOCK_TYPE_MISMATCH",
774
+ graphIndex,
775
+ nodeIndex,
776
+ path: `/graphs/${graphIndex}/nodes/${nodeIndex}`,
777
+ blockId: block.uniqueId,
778
+ message: `Unsupported extension operation "${operation}" has edited socket metadata and can no longer be exported as its canonical typed no-op.`,
779
+ });
780
+ }
781
+ }
782
+ _validateMappedBlocks(graphIndex, nodeIndex, operation, mapping, blocks, diagnostics) {
783
+ for (let role = 0; role < mapping.blocks.length; role++) {
784
+ const roleBlocks = blocks.filter((block) => _GetBlockProvenance(block)?.role === role);
785
+ if (roleBlocks.length === 0) {
786
+ _PushDiagnostic(diagnostics, {
787
+ code: "BLOCK_ROLE_MISSING",
788
+ graphIndex,
789
+ nodeIndex,
790
+ path: `/graphs/${graphIndex}/nodes/${nodeIndex}`,
791
+ message: `Composite "${operation}" is missing role ${role} (${mapping.blocks[role]}).`,
792
+ });
793
+ continue;
794
+ }
795
+ if (roleBlocks.length > 1) {
796
+ _PushDiagnostic(diagnostics, {
797
+ code: "BLOCK_ROLE_DUPLICATE",
798
+ graphIndex,
799
+ nodeIndex,
800
+ path: `/graphs/${graphIndex}/nodes/${nodeIndex}`,
801
+ message: `Composite "${operation}" has more than one block for role ${role}.`,
802
+ });
803
+ continue;
804
+ }
805
+ if (roleBlocks[0].getClassName() !== _RuntimeBlockClassName(mapping.blocks[role])) {
806
+ _PushDiagnostic(diagnostics, {
807
+ code: "BLOCK_TYPE_MISMATCH",
808
+ graphIndex,
809
+ nodeIndex,
810
+ blockId: roleBlocks[0].uniqueId,
811
+ path: `/blocks/${roleBlocks[0].uniqueId}`,
812
+ message: `Composite role ${role} must be "${mapping.blocks[role]}", not "${roleBlocks[0].getClassName()}".`,
813
+ });
814
+ }
815
+ }
816
+ for (const block of blocks) {
817
+ const provenance = _GetBlockProvenance(block);
818
+ if (!provenance || provenance.nodeIndex !== nodeIndex || provenance.operation !== operation || provenance.declarationIndex < 0) {
819
+ _PushDiagnostic(diagnostics, {
820
+ code: "BLOCK_PROVENANCE_INVALID",
821
+ graphIndex,
822
+ nodeIndex,
823
+ blockId: block.uniqueId,
824
+ path: `/blocks/${block.uniqueId}`,
825
+ message: `Block provenance no longer identifies the imported "${operation}" node.`,
826
+ });
827
+ }
828
+ else if (provenance.role < -1 || provenance.role >= mapping.blocks.length) {
829
+ _PushDiagnostic(diagnostics, {
830
+ code: "BLOCK_PROVENANCE_INVALID",
831
+ graphIndex,
832
+ nodeIndex,
833
+ blockId: block.uniqueId,
834
+ path: `/blocks/${block.uniqueId}`,
835
+ message: `Block role ${provenance.role} is not part of the "${operation}" inverse mapping.`,
836
+ });
837
+ }
838
+ }
839
+ const allowedInternalPeers = new Map();
840
+ const allowInternalConnection = (left, right) => {
841
+ const leftPeers = allowedInternalPeers.get(left) ?? new Set();
842
+ leftPeers.add(right);
843
+ allowedInternalPeers.set(left, leftPeers);
844
+ const rightPeers = allowedInternalPeers.get(right) ?? new Set();
845
+ rightPeers.add(left);
846
+ allowedInternalPeers.set(right, rightPeers);
847
+ };
848
+ for (const connector of mapping.interBlockConnectors ?? []) {
849
+ const inputBlock = _GetRoleBlock(blocks, connector.inputBlockIndex);
850
+ const outputBlock = _GetRoleBlock(blocks, connector.outputBlockIndex);
851
+ const input = connector.isVariable
852
+ ? inputBlock?.dataInputs.find((connection) => connection.name === connector.input)
853
+ : inputBlock instanceof FlowGraphExecutionBlock
854
+ ? inputBlock.signalInputs.find((connection) => connection.name === connector.input)
855
+ : undefined;
856
+ const output = connector.isVariable
857
+ ? outputBlock?.dataOutputs.find((connection) => connection.name === connector.output)
858
+ : outputBlock instanceof FlowGraphExecutionBlock
859
+ ? outputBlock.signalOutputs.find((connection) => connection.name === connector.output)
860
+ : undefined;
861
+ if (input && output) {
862
+ allowInternalConnection(input, output);
863
+ }
864
+ if (!input || !output || !input._connectedPoint.includes(output)) {
865
+ _PushDiagnostic(diagnostics, {
866
+ code: "COMPOSITE_CONNECTION_CHANGED",
867
+ graphIndex,
868
+ nodeIndex,
869
+ path: `/graphs/${graphIndex}/nodes/${nodeIndex}`,
870
+ message: `Internal composite connection "${connector.output}" -> "${connector.input}" was changed.`,
871
+ });
872
+ }
873
+ }
874
+ const reportedUnexpectedConnections = new Set();
875
+ for (const block of blocks) {
876
+ const blockProvenance = _GetBlockProvenance(block);
877
+ for (const [key, expected] of Object.entries(blockProvenance?.generatedConfiguration ?? {})) {
878
+ const current = key === "eventData"
879
+ ? NormalizeInteractivityEventDataConfiguration(block.config?.[key])
880
+ : key === "outputSignalCount"
881
+ ? (block.outputSignals?.length ??
882
+ block.executionSignals?.length ??
883
+ block.config?.[key])
884
+ : block.config?.[key];
885
+ const runtimeExpected = _GetOwn(blockProvenance?.generatedConfigurationRuntime, key) ?? (key === "eventData" ? _CreateEventSchemaRuntimeSnapshot(expected) : undefined);
886
+ const currentRuntime = runtimeExpected
887
+ ? key === "eventData"
888
+ ? _CreateEventSchemaRuntimeSnapshot(current)
889
+ : _CreateKHRInteractivityRuntimeValueSnapshot(current)
890
+ : undefined;
891
+ if (runtimeExpected
892
+ ? runtimeExpected.unrepresentable || currentRuntime?.unrepresentable || currentRuntime?.runtimeValueFingerprint !== runtimeExpected.runtimeValueFingerprint
893
+ : !_JsonEquivalent(current, expected)) {
894
+ _PushDiagnostic(diagnostics, {
895
+ code: "CONFIGURATION_UNREPRESENTABLE",
896
+ graphIndex,
897
+ nodeIndex,
898
+ blockId: block.uniqueId,
899
+ path: `/blocks/${block.uniqueId}/config/${key}`,
900
+ message: `Importer-generated configuration "${key}" was changed and no longer represents "${operation}".`,
901
+ });
902
+ }
903
+ }
904
+ for (const input of block.dataInputs) {
905
+ if (input.isConnected() || _GetSocketProvenance(input)) {
906
+ continue;
907
+ }
908
+ const expected = _GetOwn(blockProvenance?.generatedInputDefaults, input.name);
909
+ const current = _CreateKHRInteractivityRuntimeValueSnapshot(input._defaultValue);
910
+ if (!expected || expected.unrepresentable || current.unrepresentable || current.runtimeValueFingerprint !== expected.runtimeValueFingerprint) {
911
+ _PushDiagnostic(diagnostics, {
912
+ code: "INPUT_DEFAULT_UNREPRESENTABLE",
913
+ graphIndex,
914
+ nodeIndex,
915
+ blockId: block.uniqueId,
916
+ socket: input.name,
917
+ path: `/blocks/${block.uniqueId}/dataInputs/${input.name}`,
918
+ message: expected?.unrepresentable
919
+ ? `Importer-generated input default "${input.name}" could not be preserved safely for inverse export.`
920
+ : expected
921
+ ? `Importer-generated input default "${input.name}" was changed and has no KHR_interactivity inverse mapping.`
922
+ : `Input default "${input.name}" has no import-time provenance or KHR_interactivity inverse mapping.`,
923
+ });
924
+ }
925
+ }
926
+ if ((blockProvenance?.role ?? 0) < 0) {
927
+ continue;
928
+ }
929
+ const connections = [
930
+ ...block.dataInputs,
931
+ ...block.dataOutputs,
932
+ ...(block instanceof FlowGraphExecutionBlock ? block.signalInputs : []),
933
+ ...(block instanceof FlowGraphExecutionBlock ? block.signalOutputs : []),
934
+ ];
935
+ for (const connection of connections) {
936
+ if (!connection.isConnected() || reportedUnexpectedConnections.has(connection)) {
937
+ continue;
938
+ }
939
+ const khrFacing = _GetSocketProvenance(connection) ??
940
+ this._inferSocketProvenance(connection, {
941
+ sourceIndex: nodeIndex,
942
+ declarationIndex: -1,
943
+ operation,
944
+ mapping,
945
+ blocks,
946
+ classification: "exact",
947
+ diagnostics,
948
+ });
949
+ const unexpectedPeers = connection._connectedPoint.filter((peer) => {
950
+ const internal = blocks.includes(peer._ownerBlock);
951
+ const importerHelper = internal && _GetBlockProvenance(peer._ownerBlock)?.role === -1;
952
+ return internal ? !allowedInternalPeers.get(connection)?.has(peer) && !(khrFacing && importerHelper) : !khrFacing;
953
+ });
954
+ if (unexpectedPeers.length > 0) {
955
+ reportedUnexpectedConnections.add(connection);
956
+ unexpectedPeers.forEach((peer) => reportedUnexpectedConnections.add(peer));
957
+ _PushDiagnostic(diagnostics, {
958
+ code: "SOCKET_PROVENANCE_MISSING",
959
+ graphIndex,
960
+ nodeIndex,
961
+ blockId: block.uniqueId,
962
+ socket: connection.name,
963
+ path: `/blocks/${block.uniqueId}/${connection.name}`,
964
+ message: `Connection from socket "${connection.name}" is not part of the "${operation}" inverse mapping.`,
965
+ });
966
+ }
967
+ }
968
+ }
969
+ }
970
+ _buildGraph(analysis, context, diagnostics) {
971
+ const graph = CloneKHRInteractivityGraph(analysis.source);
972
+ const importedName = analysis.source.name ?? `Graph ${analysis.graphIndex + 1}`;
973
+ if (analysis.source.name !== undefined || analysis.graph.name !== importedName) {
974
+ graph.name = analysis.graph.name;
975
+ }
976
+ else {
977
+ delete graph.name;
978
+ }
979
+ this._updateVariables(graph, analysis.graph, diagnostics, analysis.graphIndex);
980
+ const logicalBySourceIndex = new Map(analysis.nodes.filter((node) => node.sourceIndex >= 0).map((node) => [node.sourceIndex, node]));
981
+ const rebuiltNodes = (graph.nodes ?? []).map((sourceNode, nodeIndex) => {
982
+ const logicalNode = logicalBySourceIndex.get(nodeIndex);
983
+ return this._rebuildNode(graph, sourceNode, logicalNode, analysis, context, diagnostics);
984
+ });
985
+ const ordered = this._topologicallyOrderNodes(rebuiltNodes, analysis.graphIndex, diagnostics);
986
+ if (!ordered) {
987
+ return graph;
988
+ }
989
+ if (analysis.source.nodes !== undefined || ordered.nodes.length > 0) {
990
+ graph.nodes = ordered.nodes;
991
+ }
992
+ else {
993
+ delete graph.nodes;
994
+ }
995
+ this._remapGraphReferences(graph, context, diagnostics, analysis.graphIndex, ordered.sourceIndices);
996
+ const validation = CreateKHRInteractivityGraphModel(graph, analysis.graphIndex, new Set(this.additionalExtensionsUsed), context.getNodeCount());
997
+ for (const diagnostic of validation.diagnostics) {
998
+ if (diagnostic.severity === "error") {
999
+ const finalNodeIndexText = /\/nodes\/(\d+)(?:\/|$)/.exec(diagnostic.path)?.[1];
1000
+ const finalNodeIndex = finalNodeIndexText === undefined ? undefined : parseInt(finalNodeIndexText, 10);
1001
+ _PushDiagnostic(diagnostics, {
1002
+ code: "GRAPH_INVALID",
1003
+ graphIndex: analysis.graphIndex,
1004
+ nodeIndex: finalNodeIndex === undefined ? undefined : ordered.sourceIndices[finalNodeIndex],
1005
+ path: diagnostic.path,
1006
+ message: diagnostic.message,
1007
+ });
1008
+ }
1009
+ }
1010
+ return graph;
1011
+ }
1012
+ _rebuildNode(graph, sourceNode, logicalNode, graphAnalysis, context, diagnostics) {
1013
+ const node = _CloneJson(sourceNode);
1014
+ const mapping = logicalNode.mapping;
1015
+ if (mapping) {
1016
+ this._rebuildConfiguration(graph, node, logicalNode, graphAnalysis.graphIndex, context, diagnostics);
1017
+ }
1018
+ for (const socket of Object.keys(sourceNode.values ?? {}).sort()) {
1019
+ if (mapping) {
1020
+ const sourceEffective = this._isEffectiveValueInput(graph, sourceNode, logicalNode, socket, false);
1021
+ const currentEffective = this._isEffectiveValueInput(graph, sourceNode, logicalNode, socket, true);
1022
+ if (!sourceEffective) {
1023
+ if (currentEffective) {
1024
+ _PushDiagnostic(diagnostics, {
1025
+ code: "CONFIGURATION_UNREPRESENTABLE",
1026
+ graphIndex: graphAnalysis.graphIndex,
1027
+ nodeIndex: logicalNode.sourceIndex,
1028
+ socket,
1029
+ path: `/graphs/${graphAnalysis.graphIndex}/nodes/${logicalNode.sourceIndex}/values/${socket}`,
1030
+ message: `Configuration edits made formerly ignored value socket "${socket}" effective without an authored FlowGraph socket.`,
1031
+ });
1032
+ }
1033
+ continue;
1034
+ }
1035
+ if (!currentEffective) {
1036
+ delete node.values[socket];
1037
+ continue;
1038
+ }
1039
+ }
1040
+ const input = _FindDataInput(logicalNode.blocks, logicalNode.sourceIndex, socket);
1041
+ if (!input) {
1042
+ _PushDiagnostic(diagnostics, {
1043
+ code: "SOCKET_PROVENANCE_MISSING",
1044
+ graphIndex: graphAnalysis.graphIndex,
1045
+ nodeIndex: logicalNode.sourceIndex,
1046
+ socket,
1047
+ path: `/graphs/${graphAnalysis.graphIndex}/nodes/${logicalNode.sourceIndex}/values/${socket}`,
1048
+ message: `Input value socket "${socket}" no longer has KHR_interactivity provenance.`,
1049
+ });
1050
+ continue;
1051
+ }
1052
+ const mappingObject = this._getMappingObject(mapping?.inputs?.values, socket);
1053
+ node.values[socket] = this._rebuildInputValue(graph, sourceNode.values[socket], input, mappingObject, logicalNode, graphAnalysis, diagnostics);
1054
+ }
1055
+ if (node.values && Object.keys(node.values).length === 0) {
1056
+ delete node.values;
1057
+ }
1058
+ const flowOutputs = new Map();
1059
+ for (const block of logicalNode.blocks) {
1060
+ if (!(block instanceof FlowGraphExecutionBlock)) {
1061
+ continue;
1062
+ }
1063
+ for (const output of block.signalOutputs) {
1064
+ const provenance = _GetSocketProvenance(output) ?? this._inferSocketProvenance(output, logicalNode);
1065
+ if (provenance?.nodeIndex === logicalNode.sourceIndex && provenance.kind === "flow" && provenance.direction === "output") {
1066
+ if (!this._isEffectiveFlowOutput(graph, sourceNode, logicalNode, provenance.socket, true)) {
1067
+ if (output.isConnected()) {
1068
+ _PushDiagnostic(diagnostics, {
1069
+ code: "CONFIGURATION_UNREPRESENTABLE",
1070
+ graphIndex: graphAnalysis.graphIndex,
1071
+ nodeIndex: logicalNode.sourceIndex,
1072
+ socket: provenance.socket,
1073
+ path: `/graphs/${graphAnalysis.graphIndex}/nodes/${logicalNode.sourceIndex}/flows/${provenance.socket}`,
1074
+ message: `Flow socket "${provenance.socket}" is connected but disabled by the edited configuration.`,
1075
+ });
1076
+ }
1077
+ continue;
1078
+ }
1079
+ if (flowOutputs.has(provenance.socket)) {
1080
+ _PushDiagnostic(diagnostics, {
1081
+ code: "SOCKET_CONNECTION_AMBIGUOUS",
1082
+ graphIndex: graphAnalysis.graphIndex,
1083
+ nodeIndex: logicalNode.sourceIndex,
1084
+ socket: provenance.socket,
1085
+ path: `/graphs/${graphAnalysis.graphIndex}/nodes/${logicalNode.sourceIndex}/flows/${provenance.socket}`,
1086
+ message: `Multiple FlowGraph outputs map to KHR flow socket "${provenance.socket}".`,
1087
+ });
1088
+ }
1089
+ else {
1090
+ flowOutputs.set(provenance.socket, output);
1091
+ }
1092
+ }
1093
+ }
1094
+ }
1095
+ const rebuiltFlows = {};
1096
+ for (const [socket, sourceFlow] of Object.entries(sourceNode.flows ?? {})) {
1097
+ const sourceEffective = this._isEffectiveFlowOutput(graph, sourceNode, logicalNode, socket, false);
1098
+ const currentEffective = this._isEffectiveFlowOutput(graph, sourceNode, logicalNode, socket, true);
1099
+ if (!sourceEffective) {
1100
+ if (currentEffective) {
1101
+ _PushDiagnostic(diagnostics, {
1102
+ code: "CONFIGURATION_UNREPRESENTABLE",
1103
+ graphIndex: graphAnalysis.graphIndex,
1104
+ nodeIndex: logicalNode.sourceIndex,
1105
+ socket,
1106
+ path: `/graphs/${graphAnalysis.graphIndex}/nodes/${logicalNode.sourceIndex}/flows/${socket}`,
1107
+ message: `Configuration edits made formerly ignored flow socket "${socket}" effective without a live FlowGraph connection.`,
1108
+ });
1109
+ }
1110
+ else {
1111
+ _SetOwnProperty(rebuiltFlows, socket, _CloneJson(sourceFlow));
1112
+ }
1113
+ }
1114
+ }
1115
+ for (const [socket, output] of [...flowOutputs.entries()].sort(([left], [right]) => left.localeCompare(right))) {
1116
+ if (output._connectedPoint.length === 0) {
1117
+ const sourceFlow = sourceNode.flows?.[socket];
1118
+ if (sourceFlow) {
1119
+ const sourceNoOp = this._isNoOpFlowTarget(graph, graphAnalysis, sourceFlow, false);
1120
+ const currentNoOp = this._isNoOpFlowTarget(graph, graphAnalysis, sourceFlow, true);
1121
+ if (sourceNoOp && currentNoOp) {
1122
+ _SetOwnProperty(rebuiltFlows, socket, _CloneJson(sourceFlow));
1123
+ }
1124
+ else if (sourceNoOp && !currentNoOp) {
1125
+ _PushDiagnostic(diagnostics, {
1126
+ code: "CONFIGURATION_UNREPRESENTABLE",
1127
+ graphIndex: graphAnalysis.graphIndex,
1128
+ nodeIndex: logicalNode.sourceIndex,
1129
+ socket,
1130
+ path: `/graphs/${graphAnalysis.graphIndex}/nodes/${logicalNode.sourceIndex}/flows/${socket}`,
1131
+ message: `Configuration edits made formerly ignored flow target "${sourceFlow.socket ?? "in"}" effective without a live FlowGraph connection.`,
1132
+ });
1133
+ }
1134
+ }
1135
+ continue;
1136
+ }
1137
+ if (output._connectedPoint.length > 1) {
1138
+ _PushDiagnostic(diagnostics, {
1139
+ code: "SOCKET_CONNECTION_AMBIGUOUS",
1140
+ graphIndex: graphAnalysis.graphIndex,
1141
+ nodeIndex: logicalNode.sourceIndex,
1142
+ socket,
1143
+ path: `/graphs/${graphAnalysis.graphIndex}/nodes/${logicalNode.sourceIndex}/flows/${socket}`,
1144
+ message: `KHR output flow "${socket}" must connect to exactly one target; found ${output._connectedPoint.length}.`,
1145
+ });
1146
+ continue;
1147
+ }
1148
+ const target = output._connectedPoint[0];
1149
+ const targetBlockProvenance = _GetBlockProvenance(target._ownerBlock);
1150
+ const targetLogicalNode = targetBlockProvenance ? graphAnalysis.nodes.find((candidate) => candidate.sourceIndex === targetBlockProvenance.nodeIndex) : undefined;
1151
+ const provenance = _GetSocketProvenance(target) ?? (targetLogicalNode ? this._inferSocketProvenance(target, targetLogicalNode) : undefined);
1152
+ if (!provenance || provenance.kind !== "flow" || provenance.direction !== "input") {
1153
+ _PushDiagnostic(diagnostics, {
1154
+ code: "SOCKET_TARGET_UNREPRESENTABLE",
1155
+ graphIndex: graphAnalysis.graphIndex,
1156
+ nodeIndex: logicalNode.sourceIndex,
1157
+ socket,
1158
+ path: `/graphs/${graphAnalysis.graphIndex}/nodes/${logicalNode.sourceIndex}/flows/${socket}`,
1159
+ message: `Flow target for "${socket}" is not part of a representable KHR node.`,
1160
+ });
1161
+ continue;
1162
+ }
1163
+ if (targetLogicalNode) {
1164
+ const targetSourceNode = graph.nodes?.[targetLogicalNode.sourceIndex];
1165
+ if (targetSourceNode && !this._isEffectiveFlowInput(graph, targetSourceNode, targetLogicalNode, provenance.socket, true)) {
1166
+ _PushDiagnostic(diagnostics, {
1167
+ code: "CONFIGURATION_UNREPRESENTABLE",
1168
+ graphIndex: graphAnalysis.graphIndex,
1169
+ nodeIndex: logicalNode.sourceIndex,
1170
+ socket,
1171
+ path: `/graphs/${graphAnalysis.graphIndex}/nodes/${logicalNode.sourceIndex}/flows/${socket}`,
1172
+ message: `Flow target socket "${provenance.socket}" is disabled by the target node's edited configuration.`,
1173
+ });
1174
+ continue;
1175
+ }
1176
+ }
1177
+ const rebuiltFlow = sourceNode.flows?.[socket]
1178
+ ? _CloneJson(sourceNode.flows[socket])
1179
+ : { node: provenance.nodeIndex };
1180
+ rebuiltFlow.node = provenance.nodeIndex;
1181
+ if (provenance.socket === "in") {
1182
+ delete rebuiltFlow.socket;
1183
+ }
1184
+ else {
1185
+ rebuiltFlow.socket = provenance.socket;
1186
+ }
1187
+ _SetOwnProperty(rebuiltFlows, socket, rebuiltFlow);
1188
+ }
1189
+ if (Object.keys(rebuiltFlows).length > 0) {
1190
+ node.flows = rebuiltFlows;
1191
+ }
1192
+ else {
1193
+ delete node.flows;
1194
+ }
1195
+ if (mapping) {
1196
+ this._remapPointerTemplateInputs(graph, node, logicalNode, graphAnalysis.graphIndex, context, diagnostics);
1197
+ }
1198
+ return node;
1199
+ }
1200
+ _getMappingObject(mapping, socket) {
1201
+ const fixed = _GetOwn(mapping, socket);
1202
+ if (fixed) {
1203
+ return fixed;
1204
+ }
1205
+ return Object.entries(mapping ?? {}).find(([key]) => key.startsWith("[") && key.endsWith("]"))?.[1];
1206
+ }
1207
+ _inferSocketProvenance(connection, logicalNode) {
1208
+ const mapping = logicalNode.mapping;
1209
+ const block = connection._ownerBlock;
1210
+ const blockProvenance = _GetBlockProvenance(block);
1211
+ if (!mapping || !blockProvenance || blockProvenance.role < 0) {
1212
+ return undefined;
1213
+ }
1214
+ const isDataInput = block.dataInputs.includes(connection);
1215
+ const isDataOutput = block.dataOutputs.includes(connection);
1216
+ const isSignalInput = block instanceof FlowGraphExecutionBlock && block.signalInputs.includes(connection);
1217
+ const isSignalOutput = block instanceof FlowGraphExecutionBlock && block.signalOutputs.includes(connection);
1218
+ const kind = isDataInput || isDataOutput ? "value" : "flow";
1219
+ const direction = isDataInput || isSignalInput ? "input" : "output";
1220
+ if (!isDataInput && !isDataOutput && !isSignalInput && !isSignalOutput) {
1221
+ return undefined;
1222
+ }
1223
+ const socketMappings = kind === "value" ? (direction === "input" ? mapping.inputs?.values : mapping.outputs?.values) : direction === "input" ? mapping.inputs?.flows : mapping.outputs?.flows;
1224
+ for (const [sourceName, property] of Object.entries(socketMappings ?? {})) {
1225
+ if (property.compatibilityOnly) {
1226
+ continue;
1227
+ }
1228
+ const expectedRole = property.toBlock ? mapping.blocks.indexOf(property.toBlock) : 0;
1229
+ if (expectedRole !== blockProvenance.role) {
1230
+ continue;
1231
+ }
1232
+ let inferredSourceName;
1233
+ if (sourceName.startsWith("[") && sourceName.endsWith("]")) {
1234
+ if (kind !== "flow" || direction !== "input") {
1235
+ continue;
1236
+ }
1237
+ const [prefix, suffix = ""] = property.name.split("$1");
1238
+ if (connection.name.startsWith(prefix) && connection.name.endsWith(suffix)) {
1239
+ inferredSourceName = connection.name.substring(prefix.length, connection.name.length - suffix.length);
1240
+ }
1241
+ }
1242
+ else if (property.name === connection.name) {
1243
+ inferredSourceName = sourceName;
1244
+ }
1245
+ if (inferredSourceName !== undefined) {
1246
+ return {
1247
+ ...blockProvenance,
1248
+ kind,
1249
+ direction,
1250
+ socket: inferredSourceName,
1251
+ };
1252
+ }
1253
+ }
1254
+ if (kind === "flow" && direction === "input" && connection.name === "in" && HasDefaultInteractivityFlowInput(logicalNode.operation)) {
1255
+ return {
1256
+ ...blockProvenance,
1257
+ kind,
1258
+ direction,
1259
+ socket: "in",
1260
+ };
1261
+ }
1262
+ return undefined;
1263
+ }
1264
+ _isNoOpFlowTarget(graph, graphAnalysis, flow, useCurrentConfiguration) {
1265
+ const targetNode = graph.nodes?.[flow.node];
1266
+ const declaration = targetNode ? graph.declarations?.[targetNode.declaration] : undefined;
1267
+ if (!declaration) {
1268
+ return false;
1269
+ }
1270
+ const mapping = getMappingForDeclaration(declaration, false);
1271
+ if (!mapping) {
1272
+ return false;
1273
+ }
1274
+ const socket = flow.socket ?? "in";
1275
+ const fixed = _GetOwn(mapping.inputs?.flows, socket);
1276
+ if (fixed && !fixed.compatibilityOnly) {
1277
+ return false;
1278
+ }
1279
+ if (Object.keys(mapping.inputs?.flows ?? {}).some((key) => key.startsWith("[") && key.endsWith("]"))) {
1280
+ const targetLogicalNode = graphAnalysis.nodes.find((candidate) => candidate.sourceIndex === flow.node);
1281
+ const allowedSockets = this._getAllowedDynamicFlowSockets(targetNode, mapping, "input", useCurrentConfiguration ? targetLogicalNode : undefined, graph);
1282
+ return !!allowedSockets && !allowedSockets.has(socket);
1283
+ }
1284
+ return !(socket === "in" && HasDefaultInteractivityFlowInput(_FullOperationName(declaration.op, declaration.extension)));
1285
+ }
1286
+ _isEffectiveFlowInput(graph, node, logicalNode, socket, useCurrentConfiguration) {
1287
+ const mapping = logicalNode.mapping;
1288
+ if (!mapping) {
1289
+ return true;
1290
+ }
1291
+ const fixed = _GetOwn(mapping.inputs?.flows, socket);
1292
+ if (fixed && !fixed.compatibilityOnly) {
1293
+ return true;
1294
+ }
1295
+ if (Object.keys(mapping.inputs?.flows ?? {}).some((key) => key.startsWith("[") && key.endsWith("]"))) {
1296
+ const allowedSockets = this._getAllowedDynamicFlowSockets(node, mapping, "input", useCurrentConfiguration ? logicalNode : undefined, graph);
1297
+ return !allowedSockets || allowedSockets.has(socket);
1298
+ }
1299
+ return socket === "in" && HasDefaultInteractivityFlowInput(logicalNode.operation);
1300
+ }
1301
+ _isEffectiveFlowOutput(graph, node, logicalNode, socket, useCurrentConfiguration) {
1302
+ const mappings = logicalNode.mapping?.outputs?.flows;
1303
+ if (!logicalNode.mapping) {
1304
+ return Object.prototype.hasOwnProperty.call(node.flows ?? {}, socket);
1305
+ }
1306
+ const fixed = _GetOwn(mappings, socket);
1307
+ if (fixed && !fixed.compatibilityOnly) {
1308
+ return true;
1309
+ }
1310
+ if (!Object.keys(mappings ?? {}).some((key) => key.startsWith("[") && key.endsWith("]"))) {
1311
+ return false;
1312
+ }
1313
+ const allowedSockets = logicalNode.mapping
1314
+ ? this._getAllowedDynamicFlowSockets(node, logicalNode.mapping, "output", useCurrentConfiguration ? logicalNode : undefined, graph)
1315
+ : undefined;
1316
+ return !allowedSockets || allowedSockets.has(socket);
1317
+ }
1318
+ _getAllowedDynamicFlowSockets(node, mapping, direction, logicalNode, graph) {
1319
+ for (const [key, property] of Object.entries(mapping.configuration ?? {})) {
1320
+ const generatesSockets = direction === "input" ? property.generatesInputFlowSockets : property.generatesOutputFlowSockets;
1321
+ if (!generatesSockets) {
1322
+ continue;
1323
+ }
1324
+ const values = graph
1325
+ ? _GetEffectiveConfigurationValue(node, mapping, key, property, graph, logicalNode, this._options.sourceGLTF?.nodes?.length)
1326
+ : node.configuration?.[key]?.value;
1327
+ const effectiveValues = values ?? (Array.isArray(property.defaultValue) ? property.defaultValue : [property.defaultValue]);
1328
+ if (direction === "input") {
1329
+ const count = typeof effectiveValues[0] === "number" && Number.isInteger(effectiveValues[0]) && effectiveValues[0] >= 0 ? effectiveValues[0] : 0;
1330
+ return new Set(Array.from({ length: count }, (_, index) => String(index)));
1331
+ }
1332
+ return new Set(effectiveValues.filter((value) => value !== undefined).map(String));
1333
+ }
1334
+ return undefined;
1335
+ }
1336
+ _resolveValueTypeIndex(graph, value, visited) {
1337
+ return "node" in value ? this._resolveOutputTypeIndex(graph, value.node, value.socket ?? "value", visited) : value.type;
1338
+ }
1339
+ _resolveOutputTypeIndex(graph, nodeIndex, socket, visited = new Set()) {
1340
+ const key = `${nodeIndex}:${socket}`;
1341
+ if (visited.has(key)) {
1342
+ return undefined;
1343
+ }
1344
+ visited.add(key);
1345
+ const node = graph.nodes?.[nodeIndex];
1346
+ const declaration = node ? graph.declarations?.[node.declaration] : undefined;
1347
+ if (!node || !declaration) {
1348
+ return undefined;
1349
+ }
1350
+ const declaredType = declaration.outputValueSockets?.[socket]?.type;
1351
+ if (declaredType !== undefined) {
1352
+ return declaredType;
1353
+ }
1354
+ if (declaration.op === "pointer/get" && socket === "value") {
1355
+ const type = node.configuration?.type?.value?.[0];
1356
+ return typeof type === "number" ? type : undefined;
1357
+ }
1358
+ if (declaration.op === "variable/get" && socket === "value") {
1359
+ const variable = node.configuration?.variable?.value?.[0];
1360
+ return typeof variable === "number" ? graph.variables?.[variable]?.type : undefined;
1361
+ }
1362
+ if (declaration.op === "math/switch" && socket === "value") {
1363
+ const defaultValue = node.values?.default;
1364
+ return defaultValue ? this._resolveValueTypeIndex(graph, defaultValue, visited) : undefined;
1365
+ }
1366
+ if (declaration.op === "event/receive" && socket !== "event") {
1367
+ const event = node.configuration?.event?.value?.[0];
1368
+ return typeof event === "number" ? graph.events?.[event]?.values?.[socket]?.type : undefined;
1369
+ }
1370
+ const mapping = getMappingForDeclaration(declaration, false);
1371
+ const outputMapping = this._getMappingObject(mapping?.outputs?.values, socket);
1372
+ const mappedType = outputMapping?.gltfType;
1373
+ const signature = mappedType === "number"
1374
+ ? "float"
1375
+ : mappedType === "boolean"
1376
+ ? "bool"
1377
+ : mappedType === "vector2"
1378
+ ? "float2"
1379
+ : mappedType === "vector3"
1380
+ ? "float3"
1381
+ : mappedType === "vector4"
1382
+ ? "float4"
1383
+ : mappedType;
1384
+ if (signature && Object.prototype.hasOwnProperty.call(gltfTypeToBabylonType, signature)) {
1385
+ const type = graph.types?.findIndex((candidate) => candidate.signature === signature) ?? -1;
1386
+ return type >= 0 ? type : undefined;
1387
+ }
1388
+ const typeSource = outputMapping?.typeSourceInput ? node.values?.[outputMapping.typeSourceInput] : undefined;
1389
+ return typeSource ? this._resolveValueTypeIndex(graph, typeSource, visited) : undefined;
1390
+ }
1391
+ _isEffectiveValueInput(graph, node, logicalNode, socket, useCurrentConfiguration) {
1392
+ const mappings = logicalNode.mapping?.inputs?.values;
1393
+ const fixed = _GetOwn(mappings, socket);
1394
+ if (fixed && !fixed.compatibilityOnly) {
1395
+ return true;
1396
+ }
1397
+ if (!Object.keys(mappings ?? {}).some((key) => key.startsWith("[") && key.endsWith("]"))) {
1398
+ return false;
1399
+ }
1400
+ const configurationValue = (key) => {
1401
+ const property = logicalNode.mapping?.configuration?.[key];
1402
+ return property
1403
+ ? _GetEffectiveConfigurationValue(node, logicalNode.mapping, key, property, graph, useCurrentConfiguration ? logicalNode : undefined, this._options.sourceGLTF?.nodes?.length)
1404
+ : undefined;
1405
+ };
1406
+ switch (logicalNode.operation) {
1407
+ case "debug/log": {
1408
+ const message = configurationValue("message")?.[0];
1409
+ return typeof message === "string" && ParseDebugLogTemplate(message).sockets.includes(socket);
1410
+ }
1411
+ case "event/send": {
1412
+ const eventIndex = configurationValue("event")?.[0];
1413
+ return typeof eventIndex === "number" && Object.prototype.hasOwnProperty.call(graph.events?.[eventIndex]?.values ?? {}, socket);
1414
+ }
1415
+ case "variable/set":
1416
+ return (configurationValue("variables") ?? []).map(String).includes(socket);
1417
+ case "math/switch":
1418
+ return (configurationValue("cases") ?? []).map(String).includes(socket);
1419
+ case "pointer/get":
1420
+ case "pointer/set":
1421
+ case "pointer/interpolate": {
1422
+ const pointer = configurationValue("pointer")?.[0];
1423
+ return (typeof pointer === "string" &&
1424
+ pointer.split("/").some((segment) => ((segment.startsWith("[") && !segment.startsWith("[[")) || (segment.startsWith("{") && !segment.startsWith("{{"))) &&
1425
+ segment
1426
+ .substring(1, segment.length - 1)
1427
+ .replace(/~1/g, "/")
1428
+ .replace(/~0/g, "~") === socket));
1429
+ }
1430
+ default:
1431
+ return true;
1432
+ }
1433
+ }
1434
+ _rebuildInputValue(graph, sourceValue, input, mappingObject, logicalNode, graphAnalysis, diagnostics) {
1435
+ let upstream = input._connectedPoint[0];
1436
+ if (input.isConnected() && mappingObject?.convertConnectedTimeToFrames) {
1437
+ upstream = this._unwrapAnimationTimeHelper(input, logicalNode, graphAnalysis, diagnostics);
1438
+ }
1439
+ if (upstream) {
1440
+ const provenance = _GetSocketProvenance(upstream);
1441
+ if (!provenance || provenance.kind !== "value" || provenance.direction !== "output") {
1442
+ _PushDiagnostic(diagnostics, {
1443
+ code: "SOCKET_TARGET_UNREPRESENTABLE",
1444
+ graphIndex: graphAnalysis.graphIndex,
1445
+ nodeIndex: logicalNode.sourceIndex,
1446
+ path: `/graphs/${graphAnalysis.graphIndex}/nodes/${logicalNode.sourceIndex}/values`,
1447
+ message: `Value input is connected to a block without representable KHR output provenance.`,
1448
+ });
1449
+ return sourceValue;
1450
+ }
1451
+ const rebuiltReference = _CloneJson(sourceValue);
1452
+ delete rebuiltReference.value;
1453
+ rebuiltReference.node = provenance.nodeIndex;
1454
+ if (provenance.socket === "value") {
1455
+ delete rebuiltReference.socket;
1456
+ }
1457
+ else {
1458
+ rebuiltReference.socket = provenance.socket;
1459
+ }
1460
+ if (rebuiltReference.type !== undefined) {
1461
+ const rebuiltType = this._resolveOutputTypeIndex(graph, provenance.nodeIndex, provenance.socket);
1462
+ if (rebuiltType !== undefined) {
1463
+ rebuiltReference.type = rebuiltType;
1464
+ }
1465
+ }
1466
+ return rebuiltReference;
1467
+ }
1468
+ const socketProvenance = _GetSocketProvenance(input);
1469
+ const sourceProvenanceValue = socketProvenance?.sourceValue;
1470
+ const current = _NormalizeValue(input._defaultValue);
1471
+ const currentSnapshot = _CreateKHRInteractivityRuntimeValueSnapshot(input._defaultValue);
1472
+ const legacyRuntimeSnapshot = sourceProvenanceValue && !("node" in sourceProvenanceValue)
1473
+ ? _CreateTypedRuntimeValueSnapshot(graph, sourceProvenanceValue.type, socketProvenance?.runtimeValue ?? sourceProvenanceValue.value)
1474
+ : undefined;
1475
+ const importedRuntimeSnapshot = socketProvenance?.runtimeValueSnapshot ?? legacyRuntimeSnapshot;
1476
+ if (!input.isConnected() &&
1477
+ importedRuntimeSnapshot &&
1478
+ !importedRuntimeSnapshot.unrepresentable &&
1479
+ !currentSnapshot.unrepresentable &&
1480
+ currentSnapshot.runtimeValueFingerprint === importedRuntimeSnapshot.runtimeValueFingerprint &&
1481
+ sourceProvenanceValue) {
1482
+ return _CloneJson(sourceProvenanceValue);
1483
+ }
1484
+ if (!input.isConnected() && socketProvenance?.runtimeValue && _ValuesEqual(current, socketProvenance.runtimeValue) && socketProvenance.sourceValue) {
1485
+ return _CloneJson(socketProvenance.sourceValue);
1486
+ }
1487
+ const canonicalSource = socketProvenance?.sourceValue ?? sourceValue;
1488
+ const type = canonicalSource.type ?? _GetOrAddInputTypeIndex(graph, input, mappingObject, logicalNode, socketProvenance?.socket ?? input.name);
1489
+ if (type === undefined || !graph.types?.[type]) {
1490
+ _PushDiagnostic(diagnostics, {
1491
+ code: "VALUE_UNREPRESENTABLE",
1492
+ graphIndex: graphAnalysis.graphIndex,
1493
+ nodeIndex: logicalNode.sourceIndex,
1494
+ path: `/graphs/${graphAnalysis.graphIndex}/nodes/${logicalNode.sourceIndex}/values`,
1495
+ message: "A newly inlined value has no canonical KHR type.",
1496
+ });
1497
+ return sourceValue;
1498
+ }
1499
+ const original = "node" in canonicalSource ? undefined : canonicalSource;
1500
+ const expected = original ? _GetSourceValue(graph, original, mappingObject, this._options.targetFps ?? 60) : undefined;
1501
+ if (_ValuesEqual(current, expected)) {
1502
+ return _CloneJson(canonicalSource);
1503
+ }
1504
+ if (!current) {
1505
+ _PushDiagnostic(diagnostics, {
1506
+ code: "VALUE_UNREPRESENTABLE",
1507
+ graphIndex: graphAnalysis.graphIndex,
1508
+ nodeIndex: logicalNode.sourceIndex,
1509
+ path: `/graphs/${graphAnalysis.graphIndex}/nodes/${logicalNode.sourceIndex}/values`,
1510
+ message: "The FlowGraph input value cannot be converted to a KHR value array.",
1511
+ });
1512
+ return sourceValue;
1513
+ }
1514
+ if (mappingObject?.convertConnectedTimeToFrames && current.length === 1 && typeof current[0] === "number") {
1515
+ current[0] /= this._options.targetFps ?? 60;
1516
+ }
1517
+ const rebuiltValue = _CloneJson(canonicalSource);
1518
+ delete rebuiltValue.node;
1519
+ delete rebuiltValue.socket;
1520
+ rebuiltValue.type = type;
1521
+ rebuiltValue.value = current;
1522
+ return rebuiltValue;
1523
+ }
1524
+ _unwrapAnimationTimeHelper(input, logicalNode, graphAnalysis, diagnostics) {
1525
+ const graphIndex = graphAnalysis.graphIndex;
1526
+ const helperOutput = input._connectedPoint[0];
1527
+ const helperBlock = helperOutput?._ownerBlock;
1528
+ const helperProvenance = helperBlock ? _GetBlockProvenance(helperBlock) : undefined;
1529
+ if (!helperBlock || helperBlock.getClassName() !== "FlowGraphMultiplyBlock" || helperProvenance?.role !== -1 || helperProvenance.nodeIndex !== logicalNode.sourceIndex) {
1530
+ _PushDiagnostic(diagnostics, {
1531
+ code: "COMPOSITE_CONNECTION_CHANGED",
1532
+ graphIndex,
1533
+ nodeIndex: logicalNode.sourceIndex,
1534
+ blockId: helperBlock?.uniqueId,
1535
+ path: `/graphs/${graphIndex}/nodes/${logicalNode.sourceIndex}`,
1536
+ message: "The generated seconds-to-frames animation helper was changed.",
1537
+ });
1538
+ return undefined;
1539
+ }
1540
+ const source = helperBlock.dataInputs.find((connection) => connection.name === "a")?._connectedPoint[0];
1541
+ const factor = helperBlock.dataInputs.find((connection) => connection.name === "b");
1542
+ const expectedFactor = this._options.targetFps ?? 60;
1543
+ const factorValue = factor ? factor._defaultValue : undefined;
1544
+ if (!factor || factor.isConnected() || _NormalizeValue(factorValue)?.[0] !== expectedFactor) {
1545
+ _PushDiagnostic(diagnostics, {
1546
+ code: "COMPOSITE_CONNECTION_CHANGED",
1547
+ graphIndex,
1548
+ nodeIndex: logicalNode.sourceIndex,
1549
+ blockId: helperBlock.uniqueId,
1550
+ path: `/blocks/${helperBlock.uniqueId}`,
1551
+ message: `Animation time helper must multiply by the target frame rate (${expectedFactor}).`,
1552
+ });
1553
+ }
1554
+ return source;
1555
+ }
1556
+ _rebuildConfiguration(graph, node, logicalNode, graphIndex, context, diagnostics) {
1557
+ const changedConfigurationGroups = new Set();
1558
+ const fallbackConfigurationGroups = new Set();
1559
+ for (const [key, property] of Object.entries(logicalNode.mapping?.configuration ?? {})) {
1560
+ if (!property.configurationGroup) {
1561
+ continue;
1562
+ }
1563
+ const block = _GetConfigurationBlock(logicalNode, property);
1564
+ const provenance = _GetConfigurationProvenance(logicalNode, key, property);
1565
+ if (provenance && !_JsonEquivalent(block?.config?.[property.name], provenance.runtimeValue)) {
1566
+ changedConfigurationGroups.add(property.configurationGroup);
1567
+ }
1568
+ if (!_IsValidConfigurationValue(provenance?.sourceValue, property, graph, this._options.sourceGLTF?.nodes?.length)) {
1569
+ fallbackConfigurationGroups.add(property.configurationGroup);
1570
+ }
1571
+ }
1572
+ for (const [key, property] of Object.entries(logicalNode.mapping?.configuration ?? {})) {
1573
+ const groupChanged = !!property.configurationGroup && changedConfigurationGroups.has(property.configurationGroup);
1574
+ const groupUsedFallback = !!property.configurationGroup && fallbackConfigurationGroups.has(property.configurationGroup);
1575
+ const provenance = _GetConfigurationProvenance(logicalNode, key, property);
1576
+ const preserveUnexposedValidMember = groupChanged &&
1577
+ !groupUsedFallback &&
1578
+ provenance !== undefined &&
1579
+ provenance.runtimeValue === undefined &&
1580
+ _IsValidConfigurationValue(provenance.sourceValue, property, graph, this._options.sourceGLTF?.nodes?.length);
1581
+ let current = _GetMappedConfigurationValue(logicalNode, key, property, graph, !groupChanged || preserveUnexposedValidMember);
1582
+ if (groupChanged && current === undefined && property.defaultValue !== undefined) {
1583
+ current = _NormalizeValue(property.defaultValue);
1584
+ }
1585
+ const source = node.configuration?.[key]?.value;
1586
+ if (property.validationOnly && current === undefined && provenance?.runtimeValue === undefined) {
1587
+ continue;
1588
+ }
1589
+ if (groupChanged && !_IsValidConfigurationValue(current, property, graph, this._options.sourceGLTF?.nodes?.length)) {
1590
+ _PushDiagnostic(diagnostics, {
1591
+ code: "CONFIGURATION_UNREPRESENTABLE",
1592
+ graphIndex,
1593
+ nodeIndex: logicalNode.sourceIndex,
1594
+ path: `/graphs/${graphIndex}/nodes/${logicalNode.sourceIndex}/configuration/${key}`,
1595
+ message: `Edited configuration group "${property.configurationGroup}" does not contain a valid value for "${key}".`,
1596
+ });
1597
+ continue;
1598
+ }
1599
+ if (!current) {
1600
+ if (source !== undefined || property.required) {
1601
+ _PushDiagnostic(diagnostics, {
1602
+ code: "CONFIGURATION_UNREPRESENTABLE",
1603
+ graphIndex,
1604
+ nodeIndex: logicalNode.sourceIndex,
1605
+ path: `/graphs/${graphIndex}/nodes/${logicalNode.sourceIndex}/configuration/${key}`,
1606
+ message: `Configuration "${key}" cannot be inverted from the edited FlowGraph composite.`,
1607
+ });
1608
+ }
1609
+ continue;
1610
+ }
1611
+ if (property.indexSource === "events" && current.length === 1 && typeof current[0] === "number") {
1612
+ const event = graph.events?.[current[0]];
1613
+ const block = _GetConfigurationBlock(logicalNode, property);
1614
+ const runtimeEventData = NormalizeInteractivityEventDataConfiguration(block?.config?.eventData);
1615
+ const runtimeSchema = runtimeEventData === undefined ? [] : runtimeEventData;
1616
+ const blockProvenance = block ? _GetBlockProvenance(block) : undefined;
1617
+ const importTimeSchema = _GetOwn(blockProvenance?.generatedConfigurationRuntime, "eventData");
1618
+ const preservesImportedEvent = provenance && _JsonEquivalent(block?.config?.[property.name], provenance.runtimeValue);
1619
+ const eventSchema = Object.entries(event?.values ?? {})
1620
+ .map(([id, value]) => ({
1621
+ id,
1622
+ type: gltfTypeToBabylonType[graph.types?.[value.type]?.signature ?? ""]?.flowGraphType,
1623
+ ...(value.value === undefined ? {} : { value: value.value.slice() }),
1624
+ }))
1625
+ .sort((left, right) => left.id.localeCompare(right.id));
1626
+ const currentSchema = _CreateEventSchemaRuntimeSnapshot(runtimeSchema);
1627
+ const expectedSchema = _CreateEventSchemaRuntimeSnapshot(eventSchema);
1628
+ const schemaMatches = preservesImportedEvent && importTimeSchema
1629
+ ? !importTimeSchema.unrepresentable && !currentSchema.unrepresentable && currentSchema.runtimeValueFingerprint === importTimeSchema.runtimeValueFingerprint
1630
+ : !currentSchema.unrepresentable && !expectedSchema.unrepresentable && currentSchema.runtimeValueFingerprint === expectedSchema.runtimeValueFingerprint;
1631
+ if (!schemaMatches) {
1632
+ _PushDiagnostic(diagnostics, {
1633
+ code: "CONFIGURATION_UNREPRESENTABLE",
1634
+ graphIndex,
1635
+ nodeIndex: logicalNode.sourceIndex,
1636
+ path: `/graphs/${graphIndex}/nodes/${logicalNode.sourceIndex}/configuration/${key}`,
1637
+ message: `The runtime custom-event payload schema does not match event index ${current[0]}.`,
1638
+ });
1639
+ continue;
1640
+ }
1641
+ }
1642
+ const preservesFallback = provenance &&
1643
+ _JsonEquivalent(_GetConfigurationBlock(logicalNode, property)?.config?.[property.name], provenance.runtimeValue) &&
1644
+ !_IsValidConfigurationValue(provenance.sourceValue, property, graph, this._options.sourceGLTF?.nodes?.length);
1645
+ if (!preservesFallback && property.indexSource === "assetNodes" && current.length === 1 && typeof current[0] === "number") {
1646
+ const sourceNode = this._options.sourceGLTF?.nodes?.[current[0]]?._babylonTransformNode;
1647
+ const remapped = sourceNode ? context.getNodeIndex(sourceNode) : undefined;
1648
+ if (remapped === undefined) {
1649
+ _PushDiagnostic(diagnostics, {
1650
+ code: "REFERENCE_UNRESOLVED",
1651
+ graphIndex,
1652
+ nodeIndex: logicalNode.sourceIndex,
1653
+ path: `/graphs/${graphIndex}/nodes/${logicalNode.sourceIndex}/configuration/${key}`,
1654
+ message: `Referenced glTF node ${current[0]} was not exported.`,
1655
+ });
1656
+ continue;
1657
+ }
1658
+ current[0] = remapped;
1659
+ }
1660
+ if (source === undefined && property.defaultValue !== undefined && _ValuesEqual(current, _NormalizeValue(property.defaultValue))) {
1661
+ continue;
1662
+ }
1663
+ node.configuration ?? (node.configuration = {});
1664
+ const rebuiltConfiguration = node.configuration[key] ? _CloneJson(node.configuration[key]) : {};
1665
+ rebuiltConfiguration.value = current;
1666
+ _SetOwnProperty(node.configuration, key, rebuiltConfiguration);
1667
+ }
1668
+ }
1669
+ _remapPointerTemplateInputs(graph, node, logicalNode, graphIndex, context, diagnostics) {
1670
+ for (const [key, property] of Object.entries(logicalNode.mapping?.configuration ?? {})) {
1671
+ if (!property.pointerTemplate) {
1672
+ continue;
1673
+ }
1674
+ const pointer = node.configuration?.[key]?.value?.[0];
1675
+ if (typeof pointer !== "string") {
1676
+ continue;
1677
+ }
1678
+ const segments = pointer.split("/");
1679
+ if (segments[0] !== "" || segments.length < 3) {
1680
+ continue;
1681
+ }
1682
+ let collection = segments[1].replace(/~1/g, "/").replace(/~0/g, "~");
1683
+ const rootPlaceholder = /^\{([^\{\}]+)\}$/.exec(segments[1]);
1684
+ if (rootPlaceholder) {
1685
+ const rootSocket = node.values?.[rootPlaceholder[1]];
1686
+ const rootReference = rootSocket && !("node" in rootSocket) ? rootSocket.value?.[0] : undefined;
1687
+ const rootMatch = typeof rootReference === "string" ? /^\/([^/]+)$/.exec(rootReference) : undefined;
1688
+ collection = rootMatch?.[1] ?? "";
1689
+ if (!_KnownIndexedRootCollections.has(collection)) {
1690
+ _PushDiagnostic(diagnostics, {
1691
+ code: "REFERENCE_UNRESOLVED",
1692
+ graphIndex,
1693
+ nodeIndex: logicalNode.sourceIndex,
1694
+ socket: rootPlaceholder[1],
1695
+ path: `/graphs/${graphIndex}/nodes/${logicalNode.sourceIndex}/values/${rootPlaceholder[1]}`,
1696
+ message: `Root collection template input "${rootPlaceholder[1]}" cannot be resolved statically for export.`,
1697
+ });
1698
+ continue;
1699
+ }
1700
+ }
1701
+ if (!_KnownIndexedRootCollections.has(collection)) {
1702
+ continue;
1703
+ }
1704
+ const indexPlaceholder = /^\[([^\[\]]+)\]$/.exec(segments[2]);
1705
+ const referenceIndexPlaceholder = /^\{([^\{\}]+)\}$/.exec(segments[2]);
1706
+ const hasTemplateCollection = !!rootPlaceholder || !!indexPlaceholder || !!referenceIndexPlaceholder;
1707
+ if (hasTemplateCollection &&
1708
+ !this._validateExtensionReferencePreservation(pointer, collection, diagnostics, graphIndex, logicalNode.sourceIndex, `/graphs/${graphIndex}/nodes/${logicalNode.sourceIndex}/configuration/${key}`)) {
1709
+ continue;
1710
+ }
1711
+ if (indexPlaceholder) {
1712
+ const socket = node.values?.[indexPlaceholder[1]];
1713
+ const path = `/graphs/${graphIndex}/nodes/${logicalNode.sourceIndex}/values/${indexPlaceholder[1]}`;
1714
+ if (!socket || "node" in socket) {
1715
+ _PushDiagnostic(diagnostics, {
1716
+ code: "REFERENCE_UNRESOLVED",
1717
+ graphIndex,
1718
+ nodeIndex: logicalNode.sourceIndex,
1719
+ socket: indexPlaceholder[1],
1720
+ path,
1721
+ message: `Dynamic index template input "${indexPlaceholder[1]}" cannot be safely remapped for the exported ${collection} array.`,
1722
+ });
1723
+ continue;
1724
+ }
1725
+ const sourceIndex = socket.value?.[0];
1726
+ const remapped = typeof sourceIndex === "number" && Number.isInteger(sourceIndex)
1727
+ ? this._getRemappedRootIndex(collection, sourceIndex, context)
1728
+ : undefined;
1729
+ if (remapped === undefined) {
1730
+ _PushDiagnostic(diagnostics, {
1731
+ code: "REFERENCE_UNRESOLVED",
1732
+ graphIndex,
1733
+ nodeIndex: logicalNode.sourceIndex,
1734
+ socket: indexPlaceholder[1],
1735
+ path,
1736
+ message: `Template input "${indexPlaceholder[1]}" does not resolve to an exported ${collection} element.`,
1737
+ });
1738
+ continue;
1739
+ }
1740
+ socket.value = [remapped];
1741
+ }
1742
+ else if (rootPlaceholder && /^(0|[1-9]\d*)$/.test(segments[2])) {
1743
+ const sourceIndex = parseInt(segments[2], 10);
1744
+ const remapped = this._getRemappedRootIndex(collection, sourceIndex, context);
1745
+ if (remapped === undefined) {
1746
+ _PushDiagnostic(diagnostics, {
1747
+ code: "REFERENCE_UNRESOLVED",
1748
+ graphIndex,
1749
+ nodeIndex: logicalNode.sourceIndex,
1750
+ path: `/graphs/${graphIndex}/nodes/${logicalNode.sourceIndex}/configuration/${key}`,
1751
+ message: `Static ${collection} index ${sourceIndex} does not resolve to an exported element.`,
1752
+ });
1753
+ }
1754
+ else {
1755
+ segments[2] = String(remapped);
1756
+ node.configuration[key].value[0] = segments.join("/");
1757
+ }
1758
+ }
1759
+ }
1760
+ }
1761
+ _updateVariables(graph, flowGraph, diagnostics, graphIndex) {
1762
+ const provenance = flowGraph.metadata?.khrInteractivity;
1763
+ if (provenance?.authoredVariableStructureChanged) {
1764
+ _PushDiagnostic(diagnostics, {
1765
+ code: "VALUE_UNREPRESENTABLE",
1766
+ graphIndex,
1767
+ path: `/graphs/${graphIndex}/variables`,
1768
+ message: "Adding, renaming, or deleting variables from an imported KHR_interactivity graph is not supported by lossless export.",
1769
+ });
1770
+ }
1771
+ const authoredValues = provenance?.authoredVariableValues;
1772
+ const authoredTypes = provenance?.authoredVariableTypes;
1773
+ if (!authoredValues && !authoredTypes) {
1774
+ return;
1775
+ }
1776
+ for (const [indexText, flowGraphType] of Object.entries(authoredTypes ?? {})) {
1777
+ const index = Number(indexText);
1778
+ const variable = graph.variables?.[index];
1779
+ const type = _GetOrAddVariableTypeIndex(graph, flowGraphType);
1780
+ if (!variable || type === undefined) {
1781
+ _PushDiagnostic(diagnostics, {
1782
+ code: "VALUE_UNREPRESENTABLE",
1783
+ graphIndex,
1784
+ path: `/graphs/${graphIndex}/variables/${index}/type`,
1785
+ message: `Authored variable type "${flowGraphType}" cannot be represented by KHR_interactivity.`,
1786
+ });
1787
+ continue;
1788
+ }
1789
+ variable.type = type;
1790
+ }
1791
+ for (const [indexText, authoredValue] of Object.entries(authoredValues ?? {})) {
1792
+ const index = Number(indexText);
1793
+ const variable = graph.variables[index];
1794
+ if (!variable) {
1795
+ _PushDiagnostic(diagnostics, {
1796
+ code: "VALUE_UNREPRESENTABLE",
1797
+ graphIndex,
1798
+ path: `/graphs/${graphIndex}/variables/${index}`,
1799
+ message: `Authored variable index ${index} does not exist in the canonical graph.`,
1800
+ });
1801
+ continue;
1802
+ }
1803
+ const normalized = _NormalizeValue(authoredValue);
1804
+ const signature = graph.types?.[variable.type]?.signature;
1805
+ if (!normalized || !signature) {
1806
+ continue;
1807
+ }
1808
+ variable.value = normalized;
1809
+ }
1810
+ }
1811
+ _remapValueArray(value, signature, context, diagnostics, graphIndex, nodeIndex, referenceHint) {
1812
+ if (signature !== "ref") {
1813
+ return value;
1814
+ }
1815
+ return value.map((entry) => {
1816
+ if (typeof entry === "string") {
1817
+ const hintedCollection = referenceHint?.match(/^\/([^/]+)\//)?.[1];
1818
+ if (hintedCollection && _KnownIndexedRootCollections.has(hintedCollection)) {
1819
+ return this._remapReferenceToCollection(entry, hintedCollection, context, diagnostics, graphIndex, nodeIndex);
1820
+ }
1821
+ return this._remapReference(entry, context, diagnostics, graphIndex, nodeIndex);
1822
+ }
1823
+ if (entry !== null && typeof entry === "object") {
1824
+ const hintedCollection = referenceHint?.match(/^\/([^/]+)\//)?.[1];
1825
+ if (hintedCollection && _KnownIndexedRootCollections.has(hintedCollection) && context.getRootIndex) {
1826
+ const index = context.getRootIndex(hintedCollection, entry);
1827
+ if (index !== undefined) {
1828
+ return `/${hintedCollection}/${index}`;
1829
+ }
1830
+ }
1831
+ const candidates = [
1832
+ ["animations", context.getAnimationIndex(entry)],
1833
+ ["cameras", context.getCameraIndex(entry)],
1834
+ ["materials", context.getMaterialIndex(entry)],
1835
+ ["nodes", context.getNodeIndex(entry)],
1836
+ ];
1837
+ const matches = (hintedCollection ? candidates.filter(([collection]) => collection === hintedCollection) : referenceHint === null ? [] : candidates).filter(([, index]) => index !== undefined);
1838
+ if (matches.length === 1) {
1839
+ return `/${matches[0][0]}/${matches[0][1]}`;
1840
+ }
1841
+ if (this._isPreflight) {
1842
+ return "";
1843
+ }
1844
+ }
1845
+ _PushDiagnostic(diagnostics, {
1846
+ code: "REFERENCE_UNRESOLVED",
1847
+ graphIndex,
1848
+ nodeIndex,
1849
+ path: `/graphs/${graphIndex}${nodeIndex === undefined ? "" : `/nodes/${nodeIndex}`}`,
1850
+ message: "An authored reference value does not resolve to an exported glTF entity.",
1851
+ });
1852
+ return "";
1853
+ });
1854
+ }
1855
+ _topologicallyOrderNodes(nodes, graphIndex, diagnostics) {
1856
+ const outgoing = nodes.map(() => new Set());
1857
+ const indegree = nodes.map(() => 0);
1858
+ let hasInvalidDependency = false;
1859
+ const addEdge = (source, target) => {
1860
+ if (source < 0 || target < 0 || source >= nodes.length || target >= nodes.length) {
1861
+ hasInvalidDependency = true;
1862
+ _PushDiagnostic(diagnostics, {
1863
+ code: "SOCKET_TARGET_UNREPRESENTABLE",
1864
+ graphIndex,
1865
+ path: `/graphs/${graphIndex}/nodes`,
1866
+ message: `Dependency ${source} -> ${target} references a node outside the exported graph.`,
1867
+ });
1868
+ return;
1869
+ }
1870
+ if (source === target) {
1871
+ hasInvalidDependency = true;
1872
+ _PushDiagnostic(diagnostics, {
1873
+ code: "DEPENDENCY_CYCLE",
1874
+ graphIndex,
1875
+ nodeIndex: source,
1876
+ path: `/graphs/${graphIndex}/nodes/${source}`,
1877
+ message: "A KHR_interactivity node cannot depend on itself.",
1878
+ });
1879
+ return;
1880
+ }
1881
+ if (outgoing[source].has(target)) {
1882
+ return;
1883
+ }
1884
+ outgoing[source].add(target);
1885
+ indegree[target]++;
1886
+ };
1887
+ for (let target = 0; target < nodes.length; target++) {
1888
+ for (const value of Object.values(nodes[target].values ?? {})) {
1889
+ if ("node" in value) {
1890
+ addEdge(value.node, target);
1891
+ }
1892
+ }
1893
+ for (const flow of Object.values(nodes[target].flows ?? {})) {
1894
+ addEdge(target, flow.node);
1895
+ }
1896
+ }
1897
+ if (hasInvalidDependency) {
1898
+ return undefined;
1899
+ }
1900
+ const ready = indegree.flatMap((count, index) => (count === 0 ? [index] : []));
1901
+ const order = [];
1902
+ while (ready.length > 0) {
1903
+ ready.sort((left, right) => left - right);
1904
+ const current = ready.shift();
1905
+ order.push(current);
1906
+ for (const target of outgoing[current]) {
1907
+ indegree[target]--;
1908
+ if (indegree[target] === 0) {
1909
+ ready.push(target);
1910
+ }
1911
+ }
1912
+ }
1913
+ if (order.length !== nodes.length) {
1914
+ _PushDiagnostic(diagnostics, {
1915
+ code: "DEPENDENCY_CYCLE",
1916
+ graphIndex,
1917
+ path: `/graphs/${graphIndex}/nodes`,
1918
+ message: "Value and flow dependencies form a cycle that cannot satisfy KHR_interactivity node ordering constraints.",
1919
+ });
1920
+ return undefined;
1921
+ }
1922
+ const remap = new Map(order.map((oldIndex, newIndex) => [oldIndex, newIndex]));
1923
+ const ordered = order.map((index) => nodes[index]);
1924
+ for (const node of ordered) {
1925
+ for (const value of Object.values(node.values ?? {})) {
1926
+ if ("node" in value) {
1927
+ value.node = remap.get(value.node);
1928
+ }
1929
+ }
1930
+ for (const flow of Object.values(node.flows ?? {})) {
1931
+ flow.node = remap.get(flow.node);
1932
+ }
1933
+ }
1934
+ return { nodes: ordered, sourceIndices: order };
1935
+ }
1936
+ _remapGraphReferences(graph, context, diagnostics, graphIndex, sourceIndices) {
1937
+ for (let variableIndex = 0; variableIndex < (graph.variables?.length ?? 0); variableIndex++) {
1938
+ const variable = graph.variables[variableIndex];
1939
+ if (graph.types?.[variable.type]?.signature === "ref" && variable.value) {
1940
+ const sourceValue = this._options.document?.graphs[graphIndex]?.source.variables?.[variableIndex]?.value?.[0];
1941
+ const currentValue = variable.value[0];
1942
+ const consumerCollection = this._getVariableReferenceCollection(graph, variableIndex);
1943
+ const referenceHint = consumerCollection === null
1944
+ ? null
1945
+ : consumerCollection
1946
+ ? `/${consumerCollection}/0`
1947
+ : typeof currentValue === "object" || currentValue === sourceValue
1948
+ ? typeof sourceValue === "string"
1949
+ ? sourceValue
1950
+ : undefined
1951
+ : undefined;
1952
+ variable.value = this._remapValueArray(variable.value, "ref", context, diagnostics, graphIndex, undefined, referenceHint);
1953
+ }
1954
+ }
1955
+ for (const event of graph.events ?? []) {
1956
+ for (const value of Object.values(event.values ?? {})) {
1957
+ if (graph.types?.[value.type]?.signature === "ref" && value.value) {
1958
+ value.value = this._remapValueArray(value.value, "ref", context, diagnostics, graphIndex);
1959
+ }
1960
+ }
1961
+ }
1962
+ for (let nodeIndex = 0; nodeIndex < (graph.nodes?.length ?? 0); nodeIndex++) {
1963
+ const node = graph.nodes[nodeIndex];
1964
+ const sourceNodeIndex = sourceIndices[nodeIndex] ?? nodeIndex;
1965
+ const declaration = graph.declarations?.[node.declaration];
1966
+ const mapping = declaration ? getMappingForDeclaration(declaration, false) : undefined;
1967
+ for (const [socket, value] of Object.entries(node.values ?? {})) {
1968
+ if (!("node" in value) && graph.types?.[value.type]?.signature === "ref" && value.value) {
1969
+ const pointerCollection = this._getPointerReferenceCollection(node, mapping, socket);
1970
+ if (pointerCollection === null) {
1971
+ _PushDiagnostic(diagnostics, {
1972
+ code: "REFERENCE_UNRESOLVED",
1973
+ graphIndex,
1974
+ nodeIndex: sourceNodeIndex,
1975
+ socket,
1976
+ path: `/graphs/${graphIndex}/nodes/${sourceNodeIndex}/values/${socket}`,
1977
+ message: `Pointer template collection for reference input "${socket}" cannot be resolved statically.`,
1978
+ });
1979
+ continue;
1980
+ }
1981
+ const sourceValue = this._options.document?.graphs[graphIndex]?.source.nodes?.[sourceNodeIndex]?.values?.[socket];
1982
+ const sourceReference = sourceValue && !("node" in sourceValue) ? sourceValue.value?.[0] : undefined;
1983
+ const currentReference = value.value[0];
1984
+ const referenceHint = pointerCollection
1985
+ ? `/${pointerCollection}/0`
1986
+ : typeof currentReference === "object" || currentReference === sourceReference
1987
+ ? typeof sourceReference === "string"
1988
+ ? sourceReference
1989
+ : undefined
1990
+ : undefined;
1991
+ value.value = this._remapValueArray(value.value, "ref", context, diagnostics, graphIndex, sourceNodeIndex, referenceHint);
1992
+ }
1993
+ }
1994
+ for (const [key, property] of Object.entries(mapping?.configuration ?? {})) {
1995
+ if (!property.pointerTemplate) {
1996
+ continue;
1997
+ }
1998
+ const configuration = node.configuration?.[key];
1999
+ if (configuration?.value) {
2000
+ configuration.value = configuration.value.map((value) => typeof value === "string" ? this._remapReference(value, context, diagnostics, graphIndex, sourceNodeIndex) : value);
2001
+ }
2002
+ }
2003
+ }
2004
+ }
2005
+ _getVariableReferenceCollection(graph, variableIndex) {
2006
+ const getterNodes = new Set();
2007
+ for (let nodeIndex = 0; nodeIndex < (graph.nodes?.length ?? 0); nodeIndex++) {
2008
+ const node = graph.nodes[nodeIndex];
2009
+ const declaration = graph.declarations?.[node.declaration];
2010
+ if (declaration?.op === "variable/get" && !declaration.extension && node.configuration?.variable?.value?.[0] === variableIndex) {
2011
+ getterNodes.add(nodeIndex);
2012
+ }
2013
+ }
2014
+ const collections = new Set();
2015
+ for (const node of graph.nodes ?? []) {
2016
+ const declaration = graph.declarations?.[node.declaration];
2017
+ const mapping = declaration ? getMappingForDeclaration(declaration, false) : undefined;
2018
+ for (const [socket, value] of Object.entries(node.values ?? {})) {
2019
+ if (!("node" in value) || !getterNodes.has(value.node)) {
2020
+ continue;
2021
+ }
2022
+ const collection = this._getPointerReferenceCollection(node, mapping, socket);
2023
+ if (collection === null) {
2024
+ return null;
2025
+ }
2026
+ if (collection) {
2027
+ collections.add(collection);
2028
+ }
2029
+ }
2030
+ }
2031
+ return collections.size === 1 ? collections.values().next().value : collections.size > 1 ? null : undefined;
2032
+ }
2033
+ _getPointerReferenceCollection(node, mapping, socket) {
2034
+ for (const [key, property] of Object.entries(mapping?.configuration ?? {})) {
2035
+ if (!property.pointerTemplate) {
2036
+ continue;
2037
+ }
2038
+ const pointer = node.configuration?.[key]?.value?.[0];
2039
+ if (typeof pointer !== "string") {
2040
+ continue;
2041
+ }
2042
+ const segments = pointer.split("/");
2043
+ const placeholderIndex = segments.findIndex((segment) => segment === `{${socket}}`);
2044
+ if (placeholderIndex > 1) {
2045
+ const parentSegment = segments[placeholderIndex - 1];
2046
+ const parentPlaceholder = /^\{([^\{\}]+)\}$/.exec(parentSegment);
2047
+ const parentValue = parentPlaceholder ? node.values?.[parentPlaceholder[1]] : undefined;
2048
+ const parentReference = parentValue && !("node" in parentValue) ? parentValue.value?.[0] : undefined;
2049
+ const collection = (parentPlaceholder && typeof parentReference === "string" ? (/^\/([^/]+)$/.exec(parentReference)?.[1] ?? "") : parentSegment)
2050
+ .replace(/~1/g, "/")
2051
+ .replace(/~0/g, "~");
2052
+ if (_KnownIndexedRootCollections.has(collection)) {
2053
+ return collection;
2054
+ }
2055
+ if (parentPlaceholder) {
2056
+ return null;
2057
+ }
2058
+ }
2059
+ }
2060
+ return undefined;
2061
+ }
2062
+ _getRemappedRootIndex(collection, sourceIndex, context) {
2063
+ const indices = new Set(this._getSourceRootObjects(collection, sourceIndex)
2064
+ .map((target) => this._getExportedObjectIndex(collection, target, context))
2065
+ .filter((index) => index !== undefined));
2066
+ return indices.size === 1 ? indices.values().next().value : undefined;
2067
+ }
2068
+ _getSourceRootObjects(collection, sourceIndex) {
2069
+ const sourceGLTF = this._options.sourceGLTF;
2070
+ switch (collection) {
2071
+ case "nodes":
2072
+ return sourceGLTF?.nodes?.[sourceIndex]?._babylonTransformNode ? [sourceGLTF.nodes[sourceIndex]._babylonTransformNode] : [];
2073
+ case "animations":
2074
+ return sourceGLTF?.animations?.[sourceIndex]?._babylonAnimationGroup ? [sourceGLTF.animations[sourceIndex]._babylonAnimationGroup] : [];
2075
+ case "cameras":
2076
+ return sourceGLTF?.cameras?.[sourceIndex]?._babylonCamera ? [sourceGLTF.cameras[sourceIndex]._babylonCamera] : [];
2077
+ case "materials":
2078
+ return Array.from(new Set(Object.values(sourceGLTF?.materials?.[sourceIndex]?._data ?? {}).map((entry) => entry.babylonMaterial)));
2079
+ case "meshes":
2080
+ return Array.from(new Set([
2081
+ ...(sourceGLTF?.meshes?.[sourceIndex]?.primitives.flatMap((primitive) => (primitive._instanceData ? [primitive._instanceData.babylonSourceMesh] : [])) ??
2082
+ []),
2083
+ ...(sourceGLTF?.nodes
2084
+ ?.filter((node) => node.mesh === sourceIndex)
2085
+ .flatMap((node) => [...(node._babylonTransformNode ? [node._babylonTransformNode] : []), ...(node._primitiveBabylonMeshes ?? [])]) ?? []),
2086
+ ]));
2087
+ case "textures":
2088
+ return sourceGLTF?.textures?.[sourceIndex]?._babylonTextures?.slice() ?? [];
2089
+ case "images":
2090
+ return Array.from(new Set(sourceGLTF?.textures
2091
+ ?.flatMap((texture) => texture._babylonTextureSources ?? [])
2092
+ .filter((source) => source.imageIndex === sourceIndex)
2093
+ .map((source) => source.babylonTexture) ?? []));
2094
+ case "samplers":
2095
+ return Array.from(new Set(sourceGLTF?.textures
2096
+ ?.flatMap((texture) => texture._babylonTextureSources ?? [])
2097
+ .filter((source) => source.samplerIndex === sourceIndex)
2098
+ .map((source) => source.babylonTexture) ?? []));
2099
+ case "skins":
2100
+ return sourceGLTF?.skins?.[sourceIndex]?._data?.babylonSkeleton ? [sourceGLTF.skins[sourceIndex]._data.babylonSkeleton] : [];
2101
+ case "scenes":
2102
+ return sourceIndex === (sourceGLTF?.scene ?? 0) && this._flowGraphs[0]?.scene ? [this._flowGraphs[0].scene] : [];
2103
+ }
2104
+ }
2105
+ _getSourceRootIndex(collection, target) {
2106
+ const roots = this._options.sourceGLTF?.[collection];
2107
+ if (!roots) {
2108
+ return undefined;
2109
+ }
2110
+ const matches = [];
2111
+ for (let index = 0; index < roots.length; index++) {
2112
+ if (this._getSourceRootObjects(collection, index).includes(target)) {
2113
+ matches.push(index);
2114
+ }
2115
+ }
2116
+ return matches.length === 1 ? matches[0] : undefined;
2117
+ }
2118
+ _getExportedObjectIndex(collection, target, context) {
2119
+ if (context.getRootIndex) {
2120
+ return context.getRootIndex(collection, target);
2121
+ }
2122
+ switch (collection) {
2123
+ case "nodes":
2124
+ return context.getNodeIndex(target);
2125
+ case "animations":
2126
+ return context.getAnimationIndex(target);
2127
+ case "cameras":
2128
+ return context.getCameraIndex(target);
2129
+ case "materials":
2130
+ return context.getMaterialIndex(target);
2131
+ default:
2132
+ return undefined;
2133
+ }
2134
+ }
2135
+ _remapReferenceToCollection(reference, targetCollection, context, diagnostics, graphIndex, nodeIndex) {
2136
+ if (!this._validateExtensionReferencePreservation(reference, targetCollection, diagnostics, graphIndex, nodeIndex)) {
2137
+ return reference;
2138
+ }
2139
+ const match = reference.match(/^\/([^/]+)\/(0|[1-9]\d*)(\/.*)?$/);
2140
+ if (!match || !_KnownIndexedRootCollections.has(match[1])) {
2141
+ return this._remapReference(reference, context, diagnostics, graphIndex, nodeIndex);
2142
+ }
2143
+ const targets = this._getSourceRootObjects(targetCollection, parseInt(match[2], 10));
2144
+ const targetIndices = new Set(targets
2145
+ .map((target) => this._getExportedObjectIndex(targetCollection, target, context))
2146
+ .filter((index) => index !== undefined));
2147
+ const targetIndex = targetIndices.size === 1 ? targetIndices.values().next().value : undefined;
2148
+ if (targetIndex === undefined) {
2149
+ _PushDiagnostic(diagnostics, {
2150
+ code: "REFERENCE_UNRESOLVED",
2151
+ graphIndex,
2152
+ nodeIndex,
2153
+ path: `/graphs/${graphIndex}${nodeIndex === undefined ? "" : `/nodes/${nodeIndex}`}`,
2154
+ message: `Reference "${reference}" cannot be remapped to the exported ${targetCollection} collection.`,
2155
+ });
2156
+ return reference;
2157
+ }
2158
+ return `/${targetCollection}/${targetIndex}${match[3] ?? ""}`;
2159
+ }
2160
+ _remapReference(reference, context, diagnostics, graphIndex, nodeIndex) {
2161
+ if (!reference || reference.startsWith("/extensions/KHR_interactivity/events/") || reference.startsWith("/extensions/KHR_interactivity/delays/")) {
2162
+ return reference;
2163
+ }
2164
+ const extensionCollection = /^\/extensions\/([^/]+)\/[^/]+\/(0|[1-9]\d*)(?:\/|$)/.exec(reference);
2165
+ if (extensionCollection && extensionCollection[1] !== "KHR_interactivity") {
2166
+ _PushDiagnostic(diagnostics, {
2167
+ code: "REFERENCE_UNRESOLVED",
2168
+ graphIndex,
2169
+ nodeIndex,
2170
+ path: `/graphs/${graphIndex}${nodeIndex === undefined ? "" : `/nodes/${nodeIndex}`}`,
2171
+ message: `Indexed reference "${reference}" belongs to ${extensionCollection[1]}, whose serializer index mapping is unavailable.`,
2172
+ });
2173
+ return reference;
2174
+ }
2175
+ const match = reference.match(/^\/([^/]+)\/(0|[1-9]\d*)(\/.*)?$/);
2176
+ if (!match || !_KnownIndexedRootCollections.has(match[1])) {
2177
+ return reference;
2178
+ }
2179
+ if (!this._validateExtensionReferencePreservation(reference, match[1], diagnostics, graphIndex, nodeIndex)) {
2180
+ return reference;
2181
+ }
2182
+ const collection = match[1];
2183
+ const sourceIndex = parseInt(match[2], 10);
2184
+ const targetIndex = this._getRemappedRootIndex(collection, sourceIndex, context);
2185
+ if (targetIndex === undefined) {
2186
+ _PushDiagnostic(diagnostics, {
2187
+ code: "REFERENCE_UNRESOLVED",
2188
+ graphIndex,
2189
+ nodeIndex,
2190
+ path: `/graphs/${graphIndex}${nodeIndex === undefined ? "" : `/nodes/${nodeIndex}`}`,
2191
+ message: `Reference "${reference}" cannot be remapped because its ${collection} target was not exported uniquely.`,
2192
+ });
2193
+ return reference;
2194
+ }
2195
+ return `/${collection}/${targetIndex}${match[3] ?? ""}`;
2196
+ }
2197
+ _validateExtensionReferencePreservation(reference, resolvedCollection, diagnostics, graphIndex, nodeIndex, path = `/graphs/${graphIndex}${nodeIndex === undefined ? "" : `/nodes/${nodeIndex}`}`) {
2198
+ const nestedExtensions = _GetPointerExtensionNames(reference).filter((extension) => extension !== "KHR_interactivity");
2199
+ if (nestedExtensions.length === 0 || _CanPreserveNestedExtensionPointer(reference, resolvedCollection)) {
2200
+ return true;
2201
+ }
2202
+ _PushDiagnostic(diagnostics, {
2203
+ code: "REFERENCE_UNRESOLVED",
2204
+ graphIndex,
2205
+ nodeIndex,
2206
+ path,
2207
+ message: `Reference "${reference}" observes extension-backed data (${nestedExtensions.join(", ")}) that the serializer cannot guarantee will retain its source semantics.`,
2208
+ });
2209
+ return false;
2210
+ }
2211
+ _collectAdditionalExtensions() {
2212
+ const extensions = new Set();
2213
+ const collectReferenceExtension = (value) => {
2214
+ if (typeof value !== "string") {
2215
+ return;
2216
+ }
2217
+ for (const extensionName of _GetPointerExtensionNames(value)) {
2218
+ if (extensionName !== "KHR_interactivity") {
2219
+ extensions.add(extensionName);
2220
+ }
2221
+ }
2222
+ };
2223
+ const collectPropertyExtensions = (value) => {
2224
+ if (Array.isArray(value)) {
2225
+ value.forEach(collectPropertyExtensions);
2226
+ return;
2227
+ }
2228
+ if (value === null || typeof value !== "object") {
2229
+ return;
2230
+ }
2231
+ const object = value;
2232
+ for (const [key, entry] of Object.entries(object)) {
2233
+ if (key === "extras") {
2234
+ continue;
2235
+ }
2236
+ if (key === "extensions" && entry !== null && typeof entry === "object" && !Array.isArray(entry)) {
2237
+ for (const [extensionName, extensionPayload] of Object.entries(entry)) {
2238
+ if (extensionName !== "KHR_interactivity") {
2239
+ extensions.add(extensionName);
2240
+ }
2241
+ collectPropertyExtensions(extensionPayload);
2242
+ }
2243
+ continue;
2244
+ }
2245
+ if (key !== "extensions") {
2246
+ collectPropertyExtensions(entry);
2247
+ }
2248
+ }
2249
+ };
2250
+ collectPropertyExtensions(this._options.document?.source);
2251
+ for (const analysis of this._graphAnalyses) {
2252
+ collectPropertyExtensions(analysis.source);
2253
+ for (const variable of analysis.source.variables ?? []) {
2254
+ if (analysis.source.types?.[variable.type]?.signature === "ref") {
2255
+ variable.value?.forEach(collectReferenceExtension);
2256
+ }
2257
+ }
2258
+ for (const event of analysis.source.events ?? []) {
2259
+ for (const value of Object.values(event.values ?? {})) {
2260
+ if (value && typeof value === "object" && analysis.source.types?.[value.type]?.signature === "ref") {
2261
+ value.value?.forEach(collectReferenceExtension);
2262
+ }
2263
+ }
2264
+ }
2265
+ for (const declaration of analysis.source.declarations ?? []) {
2266
+ if (declaration.extension) {
2267
+ extensions.add(declaration.extension);
2268
+ }
2269
+ }
2270
+ for (const node of analysis.source.nodes ?? []) {
2271
+ for (const value of Object.values(node.values ?? {})) {
2272
+ if (value && typeof value === "object" && !("node" in value) && analysis.source.types?.[value.type]?.signature === "ref") {
2273
+ value.value?.forEach(collectReferenceExtension);
2274
+ }
2275
+ }
2276
+ const declaration = analysis.source.declarations?.[node.declaration];
2277
+ const mapping = declaration ? getMappingForDeclaration(declaration, false) : undefined;
2278
+ for (const [key, property] of Object.entries(mapping?.configuration ?? {})) {
2279
+ if (property.pointerTemplate) {
2280
+ node.configuration?.[key]?.value?.forEach(collectReferenceExtension);
2281
+ }
2282
+ }
2283
+ }
2284
+ }
2285
+ for (const node of this._options.sourceGLTF?.nodes ?? []) {
2286
+ for (const extensionName of _CompanionNodeExtensions) {
2287
+ const extension = node.extensions?.[extensionName];
2288
+ if (extension) {
2289
+ extensions.add(extensionName);
2290
+ collectPropertyExtensions(extension);
2291
+ }
2292
+ }
2293
+ }
2294
+ return Array.from(extensions).sort();
2295
+ }
2296
+ _writeCompanionNodeExtensions(context, diagnostics) {
2297
+ for (let sourceIndex = 0; sourceIndex < (this._options.sourceGLTF?.nodes?.length ?? 0); sourceIndex++) {
2298
+ const sourceNode = this._options.sourceGLTF.nodes[sourceIndex];
2299
+ for (const extensionName of _CompanionNodeExtensions) {
2300
+ const value = sourceNode.extensions?.[extensionName];
2301
+ if (!value) {
2302
+ continue;
2303
+ }
2304
+ const targetIndex = sourceNode._babylonTransformNode ? context.getNodeIndex(sourceNode._babylonTransformNode) : undefined;
2305
+ if (targetIndex === undefined) {
2306
+ _PushDiagnostic(diagnostics, {
2307
+ code: "REFERENCE_UNRESOLVED",
2308
+ path: `/nodes/${sourceIndex}/extensions/${extensionName}`,
2309
+ message: `Companion extension "${extensionName}" targets a node that was not exported.`,
2310
+ });
2311
+ continue;
2312
+ }
2313
+ context.setNodeExtension(targetIndex, extensionName, _CloneJson(value));
2314
+ }
2315
+ }
2316
+ }
2317
+ _sortDiagnostics(diagnostics) {
2318
+ return diagnostics
2319
+ .slice()
2320
+ .sort((left, right) => (left.graphIndex ?? -1) - (right.graphIndex ?? -1) ||
2321
+ (left.nodeIndex ?? -1) - (right.nodeIndex ?? -1) ||
2322
+ left.path.localeCompare(right.path) ||
2323
+ left.code.localeCompare(right.code));
2324
+ }
2325
+ }
2326
+ /**
2327
+ * Creates a detached KHR_interactivity export plan without mutating the FlowGraphs.
2328
+ * @param flowGraphs graphs to export in root graph order
2329
+ * @param options canonical source and serializer settings
2330
+ * @returns reusable representability/export provider
2331
+ */
2332
+ export function CreateKHRInteractivityExportPlan(flowGraphs, options = {}) {
2333
+ return new KHRInteractivityExportPlan(flowGraphs, options);
2334
+ }
2335
+ //# sourceMappingURL=interactivityGraphExporter.js.map