@sebastianwessel/isostate 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/dist/animation/animation-engine.d.ts +3 -0
  2. package/dist/animation/animation-engine.d.ts.map +1 -1
  3. package/dist/animation/controller.d.ts +36 -2
  4. package/dist/animation/controller.d.ts.map +1 -1
  5. package/dist/assets/software-architecture/IMAGEGEN-PROMPTS.md +66 -0
  6. package/dist/assets/software-architecture/LICENSE +21 -0
  7. package/dist/assets/software-architecture/NOTICE.md +17 -0
  8. package/dist/assets/software-architecture/README.md +105 -0
  9. package/dist/assets/software-architecture/agentic/AGENT-VISUAL-PROMPTS.md +42 -0
  10. package/dist/assets/software-architecture/agentic/HANDOFF-PROMPTS.md +19 -0
  11. package/dist/assets/software-architecture/agentic/PROCESS-VISUAL-PROMPTS.md +109 -0
  12. package/dist/assets/software-architecture/agentic/agents-sprites.png +0 -0
  13. package/dist/assets/software-architecture/agentic/ai-capabilities-sprites.png +0 -0
  14. package/dist/assets/software-architecture/agentic/ai-to-human-handoff.png +0 -0
  15. package/dist/assets/software-architecture/agentic/channels-sprites.png +0 -0
  16. package/dist/assets/software-architecture/agentic/human-to-ai-handoff.png +0 -0
  17. package/dist/assets/software-architecture/agentic/humans-sprites.png +0 -0
  18. package/dist/assets/software-architecture/agentic/infrastructure-sprites.png +0 -0
  19. package/dist/assets/software-architecture/agentic/lifecycle-sprites.png +0 -0
  20. package/dist/assets/software-architecture/agentic/orchestration-sprites.png +0 -0
  21. package/dist/assets/software-architecture/agentic/providers-sprites.png +0 -0
  22. package/dist/assets/software-architecture/agentic/servicenow-sprites.png +0 -0
  23. package/dist/assets/software-architecture/architecture/architecture-sprites.png +0 -0
  24. package/dist/assets/software-architecture/manifest.json +2095 -0
  25. package/dist/assets/software-architecture/workflow/workflow-process-sprites.png +0 -0
  26. package/dist/browser/isostate.runtime.js +4 -4
  27. package/dist/browser/isostate.runtime.js.map +12 -10
  28. package/dist/chunks/errors-2yA81Vfb.js +23 -0
  29. package/dist/chunks/{errors-DyqEkrm5.js.map → errors-2yA81Vfb.js.map} +1 -1
  30. package/dist/chunks/index-WmsNtuaZ.js +1685 -0
  31. package/dist/chunks/index-WmsNtuaZ.js.map +1 -0
  32. package/dist/chunks/rendering-engine-DLaM1XbU.js +1425 -0
  33. package/dist/chunks/rendering-engine-DLaM1XbU.js.map +1 -0
  34. package/dist/chunks/scene-parser-BNXXIAqj.js +3033 -0
  35. package/dist/chunks/scene-parser-BNXXIAqj.js.map +1 -0
  36. package/dist/chunks/sha256-BkgWUdh1.js +123 -0
  37. package/dist/chunks/sha256-BkgWUdh1.js.map +1 -0
  38. package/dist/dsl/browser.js +3 -3
  39. package/dist/dsl/index.js +3 -3
  40. package/dist/dsl/scene-parser.d.ts.map +1 -1
  41. package/dist/dsl/scene-validator.d.ts.map +1 -1
  42. package/dist/dsl/workflow-effects-validation.d.ts +8 -0
  43. package/dist/dsl/workflow-effects-validation.d.ts.map +1 -0
  44. package/dist/editor-support/adapter.d.ts.map +1 -1
  45. package/dist/editor-support/index.js +347 -394
  46. package/dist/editor-support/index.js.map +1 -1
  47. package/dist/index.d.ts +4 -1
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +438 -40
  50. package/dist/index.js.map +1 -1
  51. package/dist/rendering/animation-css.d.ts.map +1 -1
  52. package/dist/rendering/rendering-engine.d.ts +11 -0
  53. package/dist/rendering/rendering-engine.d.ts.map +1 -1
  54. package/dist/rendering/scene-effects.d.ts +15 -0
  55. package/dist/rendering/scene-effects.d.ts.map +1 -0
  56. package/dist/rendering/theme.d.ts +4 -0
  57. package/dist/rendering/theme.d.ts.map +1 -1
  58. package/dist/runtime/diagnostics-overlay.d.ts +29 -0
  59. package/dist/runtime/diagnostics-overlay.d.ts.map +1 -0
  60. package/dist/runtime/export.d.ts +27 -0
  61. package/dist/runtime/export.d.ts.map +1 -0
  62. package/dist/runtime/index.d.ts +1 -0
  63. package/dist/runtime/index.d.ts.map +1 -1
  64. package/dist/runtime/index.js +4 -4
  65. package/dist/runtime/interactivity.d.ts +56 -0
  66. package/dist/runtime/interactivity.d.ts.map +1 -0
  67. package/dist/runtime/mount-scene.d.ts +11 -10
  68. package/dist/runtime/mount-scene.d.ts.map +1 -1
  69. package/dist/types/asset-registry.d.ts +1 -0
  70. package/dist/types/asset-registry.d.ts.map +1 -1
  71. package/dist/types/errors.d.ts.map +1 -1
  72. package/dist/types/index.d.ts +1 -1
  73. package/dist/types/index.d.ts.map +1 -1
  74. package/dist/types/node.d.ts +54 -1
  75. package/dist/types/node.d.ts.map +1 -1
  76. package/dist/types/runtime-bundle.d.ts +35 -0
  77. package/dist/types/runtime-bundle.d.ts.map +1 -1
  78. package/dist/types/scene.d.ts +2 -0
  79. package/dist/types/scene.d.ts.map +1 -1
  80. package/package.json +9 -8
  81. package/dist/chunks/errors-DyqEkrm5.js +0 -29
  82. package/dist/chunks/index-C3qcXbPI.js +0 -1542
  83. package/dist/chunks/index-C3qcXbPI.js.map +0 -1
  84. package/dist/chunks/rendering-engine-BUUV3Qq-.js +0 -1124
  85. package/dist/chunks/rendering-engine-BUUV3Qq-.js.map +0 -1
  86. package/dist/chunks/scene-parser-B9SPDLYs.js +0 -2688
  87. package/dist/chunks/scene-parser-B9SPDLYs.js.map +0 -1
  88. package/dist/chunks/sha256-4JuurR-b.js +0 -69
  89. package/dist/chunks/sha256-4JuurR-b.js.map +0 -1
@@ -0,0 +1,3033 @@
1
+ import { V as ValidationErrorClass, P as ParseError } from './errors-2yA81Vfb.js';
2
+ import { s as sha256 } from './sha256-BkgWUdh1.js';
3
+ import { parse } from 'yaml';
4
+
5
+ function isSafeEffectColor(value) {
6
+ return typeof value === "string" && value.trim().length > 0 && !/[<>]/.test(value) && ![...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127) && !/url\(|javascript:/i.test(value);
7
+ }
8
+ function validateActivity(activity, errors, sceneId, elementId) {
9
+ if (activity === void 0) return;
10
+ if (!["idle", "processing", "waiting", "complete", "error"].includes(activity.state) || activity.color !== void 0 && !isSafeEffectColor(activity.color)) {
11
+ errors.push({
12
+ code: "INVALID_ELEMENT_ACTIVITY",
13
+ message: "Activity requires a known state and a safe optional color",
14
+ sceneId,
15
+ elementId,
16
+ field: "activity"
17
+ });
18
+ }
19
+ }
20
+ function validateMessage(message, errors, sceneId, elementId) {
21
+ if (message === void 0) return;
22
+ const invalid = message.kind !== void 0 && !["packet", "orb", "envelope"].includes(message.kind) || message.color !== void 0 && !isSafeEffectColor(message.color) || message.enabled !== void 0 && typeof message.enabled !== "boolean" || !optionalRange(message.size, 2, 32) || !optionalRange(message.duration, 200, 3e4) || !optionalRange(message.count, 1, 4) || message.count !== void 0 && !Number.isInteger(message.count);
23
+ if (invalid)
24
+ errors.push({
25
+ code: "INVALID_CONNECTOR_MESSAGE",
26
+ message: "Message requires a known kind, safe color, size 2..32, duration 200..30000, count integer 1..4, and boolean enabled",
27
+ sceneId,
28
+ elementId,
29
+ field: "message"
30
+ });
31
+ }
32
+ function optionalRange(value, minimum, maximum) {
33
+ return value === void 0 || Number.isFinite(value) && value >= minimum && value <= maximum;
34
+ }
35
+
36
+ const BUILT_IN_TEXT_ASSET_ID$1 = "text";
37
+ const BUILT_IN_PRIMITIVE_ASSET_IDS$1 = /* @__PURE__ */ new Set(["rectangle", "circle", "polygon", "line"]);
38
+ const MAX_TEXT_CHARACTERS = 1e3;
39
+ const MAX_TEXT_LINES = 20;
40
+ const MAX_PRIMITIVE_POINTS = 100;
41
+ const VALID_ENTRY_ANIMATIONS = /* @__PURE__ */ new Set([
42
+ "fade-in",
43
+ "fade-in-grow",
44
+ "fall-in",
45
+ "rise-from-ground",
46
+ "slide-in-left",
47
+ "slide-in-right",
48
+ "flip-in",
49
+ "none"
50
+ ]);
51
+ const VALID_EXIT_ANIMATIONS = /* @__PURE__ */ new Set([
52
+ "fade-out",
53
+ "fade-out-shrink",
54
+ "fall-through-ground",
55
+ "rise-away",
56
+ "slide-out-left",
57
+ "slide-out-right",
58
+ "flip-out",
59
+ "none"
60
+ ]);
61
+ const VALID_AMBIENT_ANIMATIONS = /* @__PURE__ */ new Set([
62
+ "pulse",
63
+ "float",
64
+ "shake",
65
+ "glow",
66
+ "spin",
67
+ "blink",
68
+ "bounce"
69
+ ]);
70
+ const VALID_CONNECTOR_AMBIENT_ANIMATIONS = /* @__PURE__ */ new Set([...VALID_AMBIENT_ANIMATIONS, "flow"]);
71
+ const VALID_CONNECTOR_PATTERNS = /* @__PURE__ */ new Set(["solid", "dashed", "dotted"]);
72
+ const VALID_CONNECTOR_VARIANTS = /* @__PURE__ */ new Set(["line", "road", "beam"]);
73
+ const VALID_CONNECTOR_ENDPOINTS = /* @__PURE__ */ new Set(["none", "arrow", "dot", "circle", "diamond", "bar"]);
74
+ const VALID_CONNECTOR_DIRECTIONS = /* @__PURE__ */ new Set(["route", "reverse"]);
75
+ const VALID_CONNECTOR_SIDES = /* @__PURE__ */ new Set(["auto", "top", "right", "bottom", "left", "front", "back"]);
76
+ const VALID_CONNECTOR_ROUTING_MODES = /* @__PURE__ */ new Set(["straight", "orthogonal", "manual"]);
77
+ const VALID_CONNECTOR_ROUTING_PREFERENCES = /* @__PURE__ */ new Set(["direct", "fewest-bends", "shortest"]);
78
+ const VALID_CONNECTOR_LANES = /* @__PURE__ */ new Set(["none", "center-dashed"]);
79
+ const VALID_CAMERA_EASINGS = /* @__PURE__ */ new Set(["linear", "ease-in-out", "ease-out"]);
80
+ const VALID_SPRITE_SHEET_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".webp", ".jpg", ".jpeg", ".svg"]);
81
+ const VALID_TEXT_ALIGN = /* @__PURE__ */ new Set(["start", "middle", "end"]);
82
+ const VALID_TEXT_PLACEMENT = /* @__PURE__ */ new Set(["cell", "caption"]);
83
+ const VALID_TEXT_WEIGHT = /* @__PURE__ */ new Set(["normal", "bold"]);
84
+ const VALID_LINE_CAPS = /* @__PURE__ */ new Set(["butt", "round", "square"]);
85
+ const VALID_LINE_JOINS = /* @__PURE__ */ new Set(["miter", "round", "bevel"]);
86
+ function issue(code, message, extras = {}) {
87
+ return { code, message, ...extras };
88
+ }
89
+ function isValidIdentifier(value) {
90
+ return /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(value);
91
+ }
92
+ function isValidPosition(value) {
93
+ return Array.isArray(value) && value.length === 2 && value.every((part) => Number.isFinite(part) && part >= 0);
94
+ }
95
+ function isValidPositiveNumber(value) {
96
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
97
+ }
98
+ function defaultElementLayer(document) {
99
+ const structures = document.header.layers.find((layer) => layer.name === "structures");
100
+ return structures?.name ?? document.header.layers[0]?.name ?? "";
101
+ }
102
+ function defaultFloorLayer$1(document) {
103
+ const ground = document.header.layers.find((layer) => layer.name === "ground");
104
+ return ground?.name ?? document.header.layers[0]?.name ?? "";
105
+ }
106
+ function defaultConnectorLayer(document) {
107
+ const connectors = document.header.layers.find((layer) => layer.name === "connectors");
108
+ const ground = document.header.layers.find((layer) => layer.name === "ground");
109
+ return connectors?.name ?? ground?.name ?? document.header.layers[0]?.name ?? "";
110
+ }
111
+ function hasUrlAssetSource(document, assetId) {
112
+ return Boolean(
113
+ document.header.assetBaseUrl && document.header.assets.some((asset) => asset.id === assetId && !isSpriteSheetAsset(asset))
114
+ );
115
+ }
116
+ function isBuiltInAsset(assetId) {
117
+ return assetId === BUILT_IN_TEXT_ASSET_ID$1 || BUILT_IN_PRIMITIVE_ASSET_IDS$1.has(assetId);
118
+ }
119
+ function isPrimitiveAsset(assetId) {
120
+ return BUILT_IN_PRIMITIVE_ASSET_IDS$1.has(assetId);
121
+ }
122
+ function hasExternalAssetReferences(document) {
123
+ if (document.header.floor?.asset && !isBuiltInAsset(document.header.floor.asset)) {
124
+ return true;
125
+ }
126
+ for (const scene of document.scenes) {
127
+ for (const element of [...scene.elements ?? [], ...scene.add?.elements ?? []]) {
128
+ if (!isBuiltInAsset(element.asset)) return true;
129
+ }
130
+ }
131
+ return false;
132
+ }
133
+ function isSpriteSheetAsset(asset) {
134
+ return "type" in asset && asset.type === "sprite-sheet";
135
+ }
136
+ function buildAssetIndex(document) {
137
+ const urlAssetIds = /* @__PURE__ */ new Set();
138
+ const sheetAssetIds = /* @__PURE__ */ new Set();
139
+ const spriteAssetIds = /* @__PURE__ */ new Set();
140
+ for (const asset of document.header.assets) {
141
+ if (isSpriteSheetAsset(asset)) {
142
+ sheetAssetIds.add(asset.id);
143
+ for (const spriteId of Object.keys(asset.sprites ?? {})) spriteAssetIds.add(spriteId);
144
+ } else {
145
+ urlAssetIds.add(asset.id);
146
+ }
147
+ }
148
+ return {
149
+ urlAssetIds,
150
+ sheetAssetIds,
151
+ spriteAssetIds,
152
+ placeableAssetIds: /* @__PURE__ */ new Set([...urlAssetIds, ...spriteAssetIds])
153
+ };
154
+ }
155
+ function hasBuiltInElements(document) {
156
+ for (const scene of document.scenes) {
157
+ for (const element of [...scene.elements ?? [], ...scene.add?.elements ?? []]) {
158
+ if (isBuiltInAsset(element.asset)) return true;
159
+ }
160
+ }
161
+ return false;
162
+ }
163
+ function declaredLayerNames(document) {
164
+ return new Set(document.header.layers.map((layer) => layer.name));
165
+ }
166
+ function validateHeader(document, errors) {
167
+ const assets = document.header.assets;
168
+ if (assets.length === 0 && (hasExternalAssetReferences(document) || !hasBuiltInElements(document))) {
169
+ errors.push(issue("NO_ASSETS", "Header must declare at least one asset"));
170
+ }
171
+ const assetIndex = buildAssetIndex(document);
172
+ const assetNames = /* @__PURE__ */ new Set();
173
+ const spriteNames = /* @__PURE__ */ new Set();
174
+ for (const asset of assets) {
175
+ if (!isValidIdentifier(asset.id)) {
176
+ errors.push(
177
+ issue("INVALID_IDENTIFIER", `Asset "${asset.id}" must be kebab-case`, {
178
+ assetName: asset.id
179
+ })
180
+ );
181
+ }
182
+ if (assetNames.has(asset.id)) {
183
+ errors.push(
184
+ issue("DUPLICATE_ASSET_ID", `Duplicate asset "${asset.id}"`, {
185
+ assetName: asset.id
186
+ })
187
+ );
188
+ }
189
+ assetNames.add(asset.id);
190
+ if (spriteNames.has(asset.id)) {
191
+ errors.push(
192
+ issue("SPRITE_ASSET_ID_COLLISION", `Asset "${asset.id}" collides with a sprite id`, { assetName: asset.id })
193
+ );
194
+ }
195
+ if ("type" in asset && asset.type !== "sprite-sheet") {
196
+ errors.push(
197
+ issue("ASSET_TYPE_UNSUPPORTED", `Asset "${asset.id}" declares an unsupported type`, { assetName: asset.id })
198
+ );
199
+ }
200
+ if (isBuiltInAsset(asset.id)) {
201
+ errors.push(
202
+ issue("BUILTIN_ASSET_ID_RESERVED", `Asset "${asset.id}" is reserved for a built-in asset`, {
203
+ assetName: asset.id
204
+ })
205
+ );
206
+ continue;
207
+ }
208
+ if (isSpriteSheetAsset(asset)) {
209
+ if (!document.header.assetBaseUrl) {
210
+ errors.push(
211
+ issue("ASSET_URL_REQUIRED", `Sprite sheet "${asset.id}" has no URL source`, {
212
+ assetName: asset.id
213
+ })
214
+ );
215
+ }
216
+ validateSpriteSheetAsset(asset, assetNames, spriteNames, errors);
217
+ } else {
218
+ if (!hasUrlAssetSource(document, asset.id)) {
219
+ errors.push(
220
+ issue("ASSET_URL_REQUIRED", `Asset "${asset.id}" has no URL source`, {
221
+ assetName: asset.id
222
+ })
223
+ );
224
+ }
225
+ }
226
+ if (asset.anchor !== void 0 && (!isValidPosition(asset.anchor) || asset.anchor.some((part) => part < 0 || part > 1))) {
227
+ errors.push(
228
+ issue("INVALID_ASSET_ANCHOR", `Asset "${asset.id}" anchor must use normalized values from 0 to 1`, {
229
+ assetName: asset.id
230
+ })
231
+ );
232
+ }
233
+ }
234
+ const layers = document.header.layers;
235
+ if (layers.length === 0) {
236
+ errors.push(issue("NO_LAYERS", "Header must declare at least one layer"));
237
+ }
238
+ const layerNames = /* @__PURE__ */ new Set();
239
+ for (const layer of layers) {
240
+ if (!isValidIdentifier(layer.name)) {
241
+ errors.push(
242
+ issue("INVALID_IDENTIFIER", `Layer "${layer.name}" must be kebab-case`, {
243
+ layerName: layer.name
244
+ })
245
+ );
246
+ }
247
+ if (layerNames.has(layer.name)) {
248
+ errors.push(
249
+ issue("DUPLICATE_LAYER_NAME", `Duplicate layer "${layer.name}"`, {
250
+ layerName: layer.name
251
+ })
252
+ );
253
+ }
254
+ layerNames.add(layer.name);
255
+ if (layer.order !== void 0 && (!Number.isFinite(layer.order) || !Number.isInteger(layer.order))) {
256
+ errors.push(
257
+ issue("INVALID_LAYER_ORDER", "Layer order must be a finite integer", {
258
+ layerName: layer.name
259
+ })
260
+ );
261
+ }
262
+ }
263
+ const grid = document.header.grid;
264
+ if (grid?.cellSize !== void 0 && !(Number.isFinite(grid.cellSize) && grid.cellSize > 0)) {
265
+ errors.push(issue("INVALID_GRID_CELL_SIZE", "Grid cellSize must be a positive finite number"));
266
+ }
267
+ const floor = document.header.floor;
268
+ if (floor) {
269
+ if (floor.size !== void 0 && !isValidPositiveTuple(floor.size)) {
270
+ errors.push(issue("INVALID_FLOOR_SIZE", "Floor size must be positive"));
271
+ }
272
+ if (floor.origin !== void 0 && !floor.origin.every((part) => Number.isFinite(part))) {
273
+ errors.push(issue("INVALID_FLOOR_ORIGIN", "Floor origin must contain finite numbers"));
274
+ }
275
+ if (floor.layer !== void 0 && !layerNames.has(floor.layer)) {
276
+ errors.push(
277
+ issue("LAYER_NOT_FOUND", `Floor layer "${floor.layer}" is not declared`, {
278
+ layerName: floor.layer
279
+ })
280
+ );
281
+ }
282
+ if (floor.asset !== void 0 && !assetNames.has(floor.asset)) {
283
+ if (assetIndex.spriteAssetIds.has(floor.asset)) ; else {
284
+ errors.push(
285
+ issue("ASSET_NOT_DECLARED", `Floor asset "${floor.asset}" is not declared`, {
286
+ assetName: floor.asset
287
+ })
288
+ );
289
+ }
290
+ }
291
+ if (floor.asset !== void 0 && assetIndex.sheetAssetIds.has(floor.asset)) {
292
+ errors.push(
293
+ issue("SPRITE_SHEET_NOT_PLACEABLE", `Floor asset "${floor.asset}" is a sprite sheet namespace`, {
294
+ assetName: floor.asset
295
+ })
296
+ );
297
+ }
298
+ if (floor.asset !== void 0 && isBuiltInAsset(floor.asset)) {
299
+ errors.push(
300
+ issue("INVALID_FLOOR_ASSET", "Floor asset cannot use a built-in generated asset", { assetName: floor.asset })
301
+ );
302
+ }
303
+ }
304
+ }
305
+ function validateSpriteSheetAsset(asset, assetNames, spriteNames, errors) {
306
+ if (!hasSupportedSpriteSheetExtension(asset.path)) {
307
+ errors.push(
308
+ issue("INVALID_SPRITE_SHEET_PATH", `Sprite sheet "${asset.id}" path is unsupported`, { assetName: asset.id })
309
+ );
310
+ }
311
+ if (!isPositiveIntegerTuple(asset.sheetSize)) {
312
+ errors.push(
313
+ issue("INVALID_SPRITE_SHEET_SIZE", `Sprite sheet "${asset.id}" sheetSize is invalid`, { assetName: asset.id })
314
+ );
315
+ }
316
+ const sprites = asset.sprites ?? {};
317
+ if (Object.keys(sprites).length === 0) {
318
+ errors.push(issue("NO_SPRITES", `Sprite sheet "${asset.id}" must declare sprites`, { assetName: asset.id }));
319
+ }
320
+ const needsTileSize = Object.values(sprites).some(
321
+ (sprite) => Array.isArray(sprite) || isSpriteObject(sprite) && sprite.at !== void 0
322
+ );
323
+ if (needsTileSize && !isPositiveIntegerTuple(asset.tileSize)) {
324
+ errors.push(
325
+ issue("INVALID_SPRITE_TILE_SIZE", `Sprite sheet "${asset.id}" tileSize is required`, { assetName: asset.id })
326
+ );
327
+ }
328
+ for (const [spriteId, sprite] of Object.entries(sprites)) {
329
+ if (!isValidIdentifier(spriteId) || isBuiltInAsset(spriteId)) {
330
+ errors.push(
331
+ issue("INVALID_SPRITE_ID", `Sprite "${spriteId}" must be kebab-case and not reserved`, { assetName: spriteId })
332
+ );
333
+ }
334
+ if (spriteNames.has(spriteId)) {
335
+ errors.push(issue("DUPLICATE_SPRITE_ID", `Duplicate sprite "${spriteId}"`, { assetName: spriteId }));
336
+ }
337
+ spriteNames.add(spriteId);
338
+ if (assetNames.has(spriteId)) {
339
+ errors.push(
340
+ issue("SPRITE_ASSET_ID_COLLISION", `Sprite "${spriteId}" collides with an asset id`, { assetName: spriteId })
341
+ );
342
+ }
343
+ const rect = compileSpriteRectForValidation(sprite, asset.tileSize);
344
+ if (!rect) {
345
+ errors.push(
346
+ issue("INVALID_SPRITE_DEFINITION", `Sprite "${spriteId}" definition is invalid`, { assetName: spriteId })
347
+ );
348
+ continue;
349
+ }
350
+ if (!isRectInsideSheet(rect, asset.sheetSize)) {
351
+ errors.push(
352
+ issue("INVALID_SPRITE_RECT", `Sprite "${spriteId}" rect is outside sheet bounds`, { assetName: spriteId })
353
+ );
354
+ }
355
+ const anchor = isSpriteObject(sprite) ? sprite.anchor : void 0;
356
+ if (anchor !== void 0 && (!isValidPosition(anchor) || anchor.some((part) => part < 0 || part > 1))) {
357
+ errors.push(
358
+ issue("INVALID_ASSET_ANCHOR", `Sprite "${spriteId}" anchor must use normalized values`, {
359
+ assetName: spriteId
360
+ })
361
+ );
362
+ }
363
+ }
364
+ }
365
+ function hasSupportedSpriteSheetExtension(path) {
366
+ const extension = path.slice(path.lastIndexOf(".")).toLowerCase();
367
+ return VALID_SPRITE_SHEET_EXTENSIONS.has(extension);
368
+ }
369
+ function isPositiveIntegerTuple(value) {
370
+ return Array.isArray(value) && value.length === 2 && value.every((part) => Number.isInteger(part) && part > 0);
371
+ }
372
+ function isSpriteObject(value) {
373
+ return typeof value === "object" && value !== null && !Array.isArray(value);
374
+ }
375
+ function compileSpriteRectForValidation(sprite, tileSize) {
376
+ if (Array.isArray(sprite)) {
377
+ if (!tileSize || !isNonNegativeIntegerTuple(sprite)) return void 0;
378
+ return [sprite[0] * tileSize[0], sprite[1] * tileSize[1], tileSize[0], tileSize[1]];
379
+ }
380
+ const hasAt = sprite.at !== void 0;
381
+ const hasRect = sprite.rect !== void 0;
382
+ if (hasAt === hasRect) return void 0;
383
+ if (hasAt) {
384
+ if (!tileSize || !isNonNegativeIntegerTuple(sprite.at)) return void 0;
385
+ return [sprite.at[0] * tileSize[0], sprite.at[1] * tileSize[1], tileSize[0], tileSize[1]];
386
+ }
387
+ if (!isSpriteRect(sprite.rect)) return void 0;
388
+ return sprite.rect;
389
+ }
390
+ function isNonNegativeIntegerTuple(value) {
391
+ return Array.isArray(value) && value.length === 2 && value.every((part) => Number.isInteger(part) && part >= 0);
392
+ }
393
+ function isSpriteRect(value) {
394
+ return Array.isArray(value) && value.length === 4 && Number.isInteger(value[0]) && Number.isInteger(value[1]) && Number.isInteger(value[2]) && Number.isInteger(value[3]) && value[0] >= 0 && value[1] >= 0 && value[2] > 0 && value[3] > 0;
395
+ }
396
+ function isRectInsideSheet(rect, sheetSize) {
397
+ if (!isPositiveIntegerTuple(sheetSize)) return false;
398
+ return rect[0] + rect[2] <= sheetSize[0] && rect[1] + rect[3] <= sheetSize[1];
399
+ }
400
+ function isValidPositiveTuple(value) {
401
+ return Array.isArray(value) && value.length === 2 && value.every((part) => Number.isFinite(part) && part > 0);
402
+ }
403
+ function validateTimelineShape(document, errors) {
404
+ if (document.scenes.length === 0) {
405
+ errors.push(issue("NO_SCENES", "Document must contain at least one scene"));
406
+ return;
407
+ }
408
+ const sceneIds = /* @__PURE__ */ new Set();
409
+ for (const scene of document.scenes) {
410
+ if (!isValidIdentifier(scene.id)) {
411
+ errors.push(
412
+ issue("INVALID_IDENTIFIER", `Scene "${scene.id}" must be kebab-case`, {
413
+ sceneId: scene.id
414
+ })
415
+ );
416
+ }
417
+ if (sceneIds.has(scene.id)) {
418
+ errors.push(
419
+ issue("DUPLICATE_SCENE_ID", `Duplicate scene "${scene.id}"`, {
420
+ sceneId: scene.id
421
+ })
422
+ );
423
+ }
424
+ sceneIds.add(scene.id);
425
+ }
426
+ const first = document.scenes[0];
427
+ if (first.elements === void 0 || first.add !== void 0 || first.update !== void 0 || first.remove !== void 0) {
428
+ errors.push(
429
+ issue("INVALID_INITIAL_SCENE", "Initial scene must use elements and no delta operations", { sceneId: first.id })
430
+ );
431
+ }
432
+ for (const scene of document.scenes.slice(1)) {
433
+ if (scene.elements !== void 0 || scene.connections !== void 0) {
434
+ errors.push(
435
+ issue("INVALID_SCENE_DELTA", "Delta scenes may not use top-level elements or connections", {
436
+ sceneId: scene.id
437
+ })
438
+ );
439
+ }
440
+ }
441
+ }
442
+ function validatePlacement(placement, document, errors, warnings, sceneId) {
443
+ validateElementCommon(placement, document, errors, sceneId);
444
+ validateGeneratedContentForAsset(placement, placement.asset, errors, warnings, sceneId);
445
+ const assetIndex = buildAssetIndex(document);
446
+ if (!isBuiltInAsset(placement.asset) && assetIndex.sheetAssetIds.has(placement.asset)) {
447
+ errors.push(
448
+ issue("SPRITE_SHEET_NOT_PLACEABLE", `Asset "${placement.asset}" is a sprite sheet namespace`, {
449
+ sceneId,
450
+ elementId: placement.id,
451
+ assetName: placement.asset,
452
+ field: "asset",
453
+ value: placement.asset
454
+ })
455
+ );
456
+ } else if (!isBuiltInAsset(placement.asset) && !assetIndex.placeableAssetIds.has(placement.asset)) {
457
+ errors.push(
458
+ issue("ASSET_NOT_DECLARED", `Asset "${placement.asset}" is not declared`, {
459
+ sceneId,
460
+ elementId: placement.id,
461
+ assetName: placement.asset,
462
+ field: "asset",
463
+ value: placement.asset
464
+ })
465
+ );
466
+ }
467
+ if (!isValidPosition(placement.at)) {
468
+ errors.push(
469
+ issue("INVALID_POSITION", "Element at must be finite and non-negative", {
470
+ sceneId,
471
+ elementId: placement.id,
472
+ field: "at",
473
+ value: placement.at
474
+ })
475
+ );
476
+ }
477
+ }
478
+ function validatePatch(patch, document, errors, warnings, sceneId, currentAsset) {
479
+ validateElementCommon(patch, document, errors, sceneId, true);
480
+ if (patch.asset !== void 0) validateAssetSwap(patch, currentAsset, document, errors, sceneId);
481
+ if (currentAsset !== void 0) {
482
+ validateGeneratedContentForAsset(patch, patch.asset ?? currentAsset, errors, warnings, sceneId, true);
483
+ }
484
+ if (patch.at !== void 0 && !isValidPosition(patch.at)) {
485
+ errors.push(
486
+ issue("INVALID_POSITION", "Element at must be finite and non-negative", {
487
+ sceneId,
488
+ elementId: patch.id,
489
+ field: "at",
490
+ value: patch.at
491
+ })
492
+ );
493
+ }
494
+ }
495
+ function validateAssetSwap(patch, currentAsset, document, errors, sceneId) {
496
+ const target = patch.asset;
497
+ if (target === void 0) return;
498
+ const extras = { sceneId, elementId: patch.id, assetName: target, field: "asset", value: target };
499
+ if (isBuiltInAsset(target) || currentAsset !== void 0 && isBuiltInAsset(currentAsset)) {
500
+ errors.push(
501
+ issue(
502
+ "INVALID_ELEMENT_ASSET_SWAP",
503
+ "Asset updates only support external images or sprites; generated content cannot change asset type",
504
+ extras
505
+ )
506
+ );
507
+ return;
508
+ }
509
+ const index = buildAssetIndex(document);
510
+ if (index.sheetAssetIds.has(target))
511
+ errors.push(issue("SPRITE_SHEET_NOT_PLACEABLE", "A sprite sheet namespace cannot be placed", extras));
512
+ else if (!index.placeableAssetIds.has(target))
513
+ errors.push(issue("ASSET_NOT_DECLARED", `Asset "${target}" is not declared`, extras));
514
+ }
515
+ function validateElementCommon(element, document, errors, sceneId, allowZeroSize = false) {
516
+ if (!isValidIdentifier(element.id)) {
517
+ errors.push(
518
+ issue("INVALID_IDENTIFIER", `Element "${element.id}" must be kebab-case`, {
519
+ sceneId,
520
+ elementId: element.id
521
+ })
522
+ );
523
+ }
524
+ const minSize = allowZeroSize ? 0 : 1;
525
+ if (element.size !== void 0 && (!Number.isFinite(element.size) || element.size < minSize)) {
526
+ errors.push(
527
+ issue(
528
+ "INVALID_SIZE",
529
+ allowZeroSize ? "Element size must be zero or greater" : "Element size must be greater than zero",
530
+ {
531
+ sceneId,
532
+ elementId: element.id,
533
+ field: "size",
534
+ value: element.size
535
+ }
536
+ )
537
+ );
538
+ }
539
+ if (element.size !== void 0 && (!Number.isInteger(element.size) || element.size < minSize)) {
540
+ errors.push(
541
+ issue("INVALID_SIZE", "Element size must be a whole grid cell count", {
542
+ sceneId,
543
+ elementId: element.id,
544
+ field: "size",
545
+ value: element.size
546
+ })
547
+ );
548
+ }
549
+ if (element.layer !== void 0 && !declaredLayerNames(document).has(element.layer)) {
550
+ errors.push(
551
+ issue("LAYER_NOT_FOUND", `Layer "${element.layer}" is not declared`, {
552
+ sceneId,
553
+ elementId: element.id,
554
+ layerName: element.layer,
555
+ field: "layer",
556
+ value: element.layer
557
+ })
558
+ );
559
+ }
560
+ if (element.enter !== void 0 && !VALID_ENTRY_ANIMATIONS.has(element.enter)) {
561
+ errors.push(
562
+ issue("UNKNOWN_ANIMATION", `Unknown entry animation "${element.enter}"`, {
563
+ sceneId,
564
+ elementId: element.id,
565
+ field: "enter",
566
+ value: element.enter
567
+ })
568
+ );
569
+ }
570
+ if (element.exit !== void 0 && !VALID_EXIT_ANIMATIONS.has(element.exit)) {
571
+ errors.push(
572
+ issue("UNKNOWN_ANIMATION", `Unknown exit animation "${element.exit}"`, {
573
+ sceneId,
574
+ elementId: element.id,
575
+ field: "exit",
576
+ value: element.exit
577
+ })
578
+ );
579
+ }
580
+ validateActivity(element.activity, errors, sceneId, element.id);
581
+ validateAmbient(element.ambient, errors, sceneId, element.id);
582
+ }
583
+ function validateGeneratedContentForAsset(element, assetId, errors, warnings, sceneId, allowSparse = false) {
584
+ if (isBuiltInAsset(assetId)) {
585
+ if (assetId === BUILT_IN_TEXT_ASSET_ID$1) {
586
+ validateTextForAsset(element, errors, warnings, sceneId, allowSparse);
587
+ return;
588
+ }
589
+ validatePrimitiveForAsset(element, assetId, errors, sceneId, allowSparse);
590
+ return;
591
+ }
592
+ if (element.text !== void 0) {
593
+ errors.push(
594
+ issue("TEXT_CONTENT_FOR_NON_TEXT_ASSET", "Only built-in text elements may define text content", {
595
+ sceneId,
596
+ elementId: element.id,
597
+ assetName: assetId,
598
+ field: "text"
599
+ })
600
+ );
601
+ }
602
+ if (element.primitive !== void 0) {
603
+ errors.push(
604
+ issue("GENERATED_CONTENT_FOR_EXTERNAL_ASSET", "Only built-in generated assets may define primitive content", {
605
+ sceneId,
606
+ elementId: element.id,
607
+ assetName: assetId,
608
+ field: "primitive"
609
+ })
610
+ );
611
+ }
612
+ }
613
+ function validateTextForAsset(element, errors, warnings, sceneId, allowSparse) {
614
+ if (element.primitive !== void 0) {
615
+ errors.push(
616
+ issue("PRIMITIVE_CONTENT_FOR_TEXT_ASSET", "Built-in text elements may not define primitive content", {
617
+ sceneId,
618
+ elementId: element.id,
619
+ field: "primitive"
620
+ })
621
+ );
622
+ }
623
+ if (!element.text) {
624
+ if (allowSparse) return;
625
+ errors.push(
626
+ issue("TEXT_CONTENT_REQUIRED", "Built-in text elements require text content", {
627
+ sceneId,
628
+ elementId: element.id,
629
+ field: "text"
630
+ })
631
+ );
632
+ return;
633
+ }
634
+ validateTextContent(element.text, errors, warnings, sceneId, element.id, allowSparse);
635
+ }
636
+ function validatePrimitiveForAsset(element, assetId, errors, sceneId, allowSparse) {
637
+ if (!isPrimitiveAsset(assetId)) return;
638
+ if (element.text !== void 0) {
639
+ errors.push(
640
+ issue("TEXT_CONTENT_FOR_PRIMITIVE_ASSET", "Primitive elements may not define text content", {
641
+ sceneId,
642
+ elementId: element.id,
643
+ field: "text"
644
+ })
645
+ );
646
+ }
647
+ const primitive = element.primitive;
648
+ if (!primitive) {
649
+ if (allowSparse) return;
650
+ errors.push(
651
+ issue("PRIMITIVE_CONTENT_REQUIRED", "Built-in primitive elements require primitive content", {
652
+ sceneId,
653
+ elementId: element.id,
654
+ assetName: assetId,
655
+ field: "primitive"
656
+ })
657
+ );
658
+ return;
659
+ }
660
+ const activeKeys = Object.entries(primitive).filter(([, value]) => value !== void 0).map(([key]) => key);
661
+ if (activeKeys.length !== 1 || activeKeys[0] !== assetId) {
662
+ errors.push(
663
+ issue("PRIMITIVE_CONTENT_MISMATCH", "Primitive content must define exactly the payload matching its asset id", {
664
+ sceneId,
665
+ elementId: element.id,
666
+ assetName: assetId,
667
+ field: "primitive",
668
+ value: activeKeys
669
+ })
670
+ );
671
+ return;
672
+ }
673
+ const payload = primitive[assetId];
674
+ validatePrimitiveStyle(payload, errors, sceneId, element.id, `primitive.${assetId}`);
675
+ if (assetId === "rectangle") {
676
+ const rx = primitive.rectangle?.rx;
677
+ if (rx !== void 0 && (!Number.isFinite(rx) || rx < 0 || rx > 0.5)) {
678
+ errors.push(
679
+ issue("INVALID_PRIMITIVE_STYLE", "Rectangle rx must be from 0 to 0.5", {
680
+ sceneId,
681
+ elementId: element.id,
682
+ field: "primitive.rectangle.rx",
683
+ value: rx
684
+ })
685
+ );
686
+ }
687
+ }
688
+ if (assetId === "polygon" && (!allowSparse || primitive.polygon?.points !== void 0)) {
689
+ validatePrimitivePoints(primitive.polygon?.points, 3, errors, sceneId, element.id);
690
+ }
691
+ if (assetId === "line") {
692
+ if (!allowSparse || primitive.line?.points !== void 0) {
693
+ validatePrimitivePoints(primitive.line?.points, 2, errors, sceneId, element.id);
694
+ }
695
+ if (primitive.line?.lineCap !== void 0 && !VALID_LINE_CAPS.has(primitive.line.lineCap)) {
696
+ errors.push(
697
+ issue("INVALID_PRIMITIVE_STYLE", "Line cap is invalid", {
698
+ sceneId,
699
+ elementId: element.id,
700
+ field: "primitive.line.lineCap",
701
+ value: primitive.line.lineCap
702
+ })
703
+ );
704
+ }
705
+ if (primitive.line?.lineJoin !== void 0 && !VALID_LINE_JOINS.has(primitive.line.lineJoin)) {
706
+ errors.push(
707
+ issue("INVALID_PRIMITIVE_STYLE", "Line join is invalid", {
708
+ sceneId,
709
+ elementId: element.id,
710
+ field: "primitive.line.lineJoin",
711
+ value: primitive.line.lineJoin
712
+ })
713
+ );
714
+ }
715
+ }
716
+ }
717
+ function validatePrimitiveStyle(style, errors, sceneId, elementId, fieldPrefix) {
718
+ if (!style) return;
719
+ const colorFields = [
720
+ ["stroke", style.stroke],
721
+ ["fill", "fill" in style ? style.fill : void 0]
722
+ ];
723
+ for (const [name, token] of colorFields) {
724
+ if (token !== void 0 && !isSafeTextStyleToken(token)) {
725
+ errors.push(
726
+ issue("INVALID_PRIMITIVE_STYLE", "Primitive color token is unsafe", {
727
+ sceneId,
728
+ elementId,
729
+ field: `${fieldPrefix}.${name}`,
730
+ value: token
731
+ })
732
+ );
733
+ }
734
+ }
735
+ if (style.strokeWidth !== void 0 && (!Number.isFinite(style.strokeWidth) || style.strokeWidth < 0)) {
736
+ errors.push(
737
+ issue("INVALID_PRIMITIVE_STYLE", "Primitive strokeWidth is invalid", {
738
+ sceneId,
739
+ elementId,
740
+ field: `${fieldPrefix}.strokeWidth`,
741
+ value: style.strokeWidth
742
+ })
743
+ );
744
+ }
745
+ if (style.opacity !== void 0 && (!Number.isFinite(style.opacity) || style.opacity < 0 || style.opacity > 1)) {
746
+ errors.push(
747
+ issue("INVALID_PRIMITIVE_STYLE", "Primitive opacity must be 0..1", {
748
+ sceneId,
749
+ elementId,
750
+ field: `${fieldPrefix}.opacity`,
751
+ value: style.opacity
752
+ })
753
+ );
754
+ }
755
+ if (style.dash !== void 0 && (!isValidPositiveTuple(style.dash) || style.dash.some((part) => !Number.isFinite(part)))) {
756
+ errors.push(
757
+ issue("INVALID_PRIMITIVE_STYLE", "Primitive dash is invalid", {
758
+ sceneId,
759
+ elementId,
760
+ field: `${fieldPrefix}.dash`,
761
+ value: style.dash
762
+ })
763
+ );
764
+ }
765
+ }
766
+ function validatePrimitivePoints(points, minCount, errors, sceneId, elementId) {
767
+ if (!points || points.length < minCount || points.length > MAX_PRIMITIVE_POINTS || points.some((point) => !isValidPosition(point) || point.some((part) => part < 0 || part > 1))) {
768
+ errors.push(
769
+ issue("INVALID_PRIMITIVE_POINTS", "Primitive points must use normalized coordinates from 0 to 1", {
770
+ sceneId,
771
+ elementId,
772
+ field: "primitive.points",
773
+ value: points
774
+ })
775
+ );
776
+ }
777
+ }
778
+ function validateTextContent(text, errors, warnings, sceneId, elementId, allowSparse = false) {
779
+ if (text.value === void 0) {
780
+ if (!allowSparse) {
781
+ errors.push(
782
+ issue("INVALID_TEXT_CONTENT", "Text content must define a value", {
783
+ sceneId,
784
+ elementId,
785
+ field: "text.value"
786
+ })
787
+ );
788
+ }
789
+ } else {
790
+ const value = normalizeTextValue(text.value);
791
+ const lines = value.split("\n");
792
+ if (value.length === 0 || lines.every((line) => line.trim().length === 0)) {
793
+ warnings.push(
794
+ issue("EMPTY_TEXT_CONTENT", "Text value is empty and will render no visible label", {
795
+ sceneId,
796
+ elementId,
797
+ field: "text.value",
798
+ value: text.value
799
+ })
800
+ );
801
+ }
802
+ if (value.length > MAX_TEXT_CHARACTERS || lines.length > MAX_TEXT_LINES) {
803
+ errors.push(
804
+ issue(
805
+ "INVALID_TEXT_CONTENT",
806
+ `Text content must be at most ${MAX_TEXT_CHARACTERS} characters and at most ${MAX_TEXT_LINES} lines`,
807
+ { sceneId, elementId, field: "text.value" }
808
+ )
809
+ );
810
+ }
811
+ }
812
+ if (text.align !== void 0 && !VALID_TEXT_ALIGN.has(text.align)) {
813
+ errors.push(
814
+ issue("INVALID_TEXT_STYLE", "Text align must be start, middle, or end", {
815
+ sceneId,
816
+ elementId,
817
+ field: "text.align",
818
+ value: text.align
819
+ })
820
+ );
821
+ }
822
+ if (text.placement !== void 0 && !VALID_TEXT_PLACEMENT.has(text.placement)) {
823
+ errors.push(
824
+ issue("INVALID_TEXT_STYLE", "Text placement must be cell or caption", {
825
+ sceneId,
826
+ elementId,
827
+ field: "text.placement",
828
+ value: text.placement
829
+ })
830
+ );
831
+ }
832
+ if (text.fontSize !== void 0 && !isValidPositiveNumber(text.fontSize)) {
833
+ errors.push(
834
+ issue("INVALID_TEXT_STYLE", "Text fontSize must be greater than zero", {
835
+ sceneId,
836
+ elementId,
837
+ field: "text.fontSize",
838
+ value: text.fontSize
839
+ })
840
+ );
841
+ }
842
+ if (text.lineHeight !== void 0 && !isValidPositiveNumber(text.lineHeight)) {
843
+ errors.push(
844
+ issue("INVALID_TEXT_STYLE", "Text lineHeight must be greater than zero", {
845
+ sceneId,
846
+ elementId,
847
+ field: "text.lineHeight",
848
+ value: text.lineHeight
849
+ })
850
+ );
851
+ }
852
+ if (text.fontWeight !== void 0 && !isValidTextWeight(text.fontWeight)) {
853
+ errors.push(
854
+ issue("INVALID_TEXT_STYLE", "Text fontWeight must be normal, bold, or a positive finite number", {
855
+ sceneId,
856
+ elementId,
857
+ field: "text.fontWeight",
858
+ value: text.fontWeight
859
+ })
860
+ );
861
+ }
862
+ if (text.fill !== void 0 && !isSafeTextStyleToken(text.fill)) {
863
+ errors.push(
864
+ issue("INVALID_TEXT_STYLE", "Text fill contains unsafe CSS syntax", {
865
+ sceneId,
866
+ elementId,
867
+ field: "text.fill",
868
+ value: text.fill
869
+ })
870
+ );
871
+ }
872
+ }
873
+ function normalizeTextValue(value) {
874
+ return value.replace(/\r\n?/g, "\n");
875
+ }
876
+ function isValidTextWeight(value) {
877
+ if (typeof value === "number") {
878
+ return Number.isFinite(value) && value > 0;
879
+ }
880
+ return typeof value === "string" && VALID_TEXT_WEIGHT.has(value);
881
+ }
882
+ function isSafeTextStyleToken(value) {
883
+ const normalized = value.trim().toLowerCase();
884
+ return normalized.length > 0 && !normalized.includes("url(") && !normalized.includes("javascript:") && !value.includes("<") && !value.includes(">") && !hasControlCharacters(value);
885
+ }
886
+ function hasControlCharacters(value) {
887
+ for (const char of value) {
888
+ const code = char.charCodeAt(0);
889
+ if (code < 32 || code === 127) return true;
890
+ }
891
+ return false;
892
+ }
893
+ function validateRemoval(removal, errors, sceneId) {
894
+ if (!isValidIdentifier(removal.id)) {
895
+ errors.push(
896
+ issue("INVALID_IDENTIFIER", `Element "${removal.id}" must be kebab-case`, {
897
+ sceneId,
898
+ elementId: removal.id
899
+ })
900
+ );
901
+ }
902
+ if (removal.exit !== void 0 && !VALID_EXIT_ANIMATIONS.has(removal.exit)) {
903
+ errors.push(
904
+ issue("UNKNOWN_ANIMATION", `Unknown exit animation "${removal.exit}"`, {
905
+ sceneId,
906
+ elementId: removal.id
907
+ })
908
+ );
909
+ }
910
+ }
911
+ function validateAmbient(ambient, errors, sceneId, elementId) {
912
+ validateAmbientWithSet(ambient, VALID_AMBIENT_ANIMATIONS, errors, sceneId, elementId);
913
+ }
914
+ function validateAmbientWithSet(ambient, validAnimations, errors, sceneId, elementId) {
915
+ for (const animation of ambient ?? []) {
916
+ if (!validAnimations.has(animation.name)) {
917
+ errors.push(
918
+ issue("UNKNOWN_AMBIENT_ANIMATION", `Unknown ambient animation "${animation.name}"`, { sceneId, elementId })
919
+ );
920
+ }
921
+ if (animation.infinite === false && (animation.iterations === void 0 || !Number.isInteger(animation.iterations) || animation.iterations <= 0)) {
922
+ errors.push(
923
+ issue("INVALID_AMBIENT_ITERATIONS", "Ambient iterations must be positive when infinite is false", {
924
+ sceneId,
925
+ elementId
926
+ })
927
+ );
928
+ }
929
+ }
930
+ }
931
+ function validateSceneObjectDeltas(document, errors, warnings) {
932
+ const elements = /* @__PURE__ */ new Map();
933
+ const connectors = /* @__PURE__ */ new Map();
934
+ const documentElementIds = collectDocumentElementIds(document);
935
+ const first = document.scenes[0];
936
+ if (!first?.elements) return;
937
+ for (const element of first.elements) {
938
+ validatePlacement(element, document, errors, warnings, first.id);
939
+ if (elements.has(element.id)) {
940
+ errors.push(
941
+ issue("DUPLICATE_ELEMENT_ID", `Duplicate element "${element.id}"`, {
942
+ sceneId: first.id,
943
+ elementId: element.id
944
+ })
945
+ );
946
+ }
947
+ elements.set(element.id, normalizePlacement(document, element));
948
+ }
949
+ for (const connection of first.connections ?? []) {
950
+ validateConnectionPlacement(connection, document, errors, first.id, elements, documentElementIds);
951
+ if (connectors.has(connection.id)) {
952
+ errors.push(
953
+ issue("DUPLICATE_CONNECTOR_ID", `Duplicate connection "${connection.id}"`, {
954
+ sceneId: first.id,
955
+ elementId: connection.id
956
+ })
957
+ );
958
+ }
959
+ connectors.set(connection.id, normalizeConnectionPlacement(document, connection));
960
+ }
961
+ validateCamera(first.camera, first.id, elements, connectors, errors);
962
+ for (const scene of document.scenes.slice(1)) {
963
+ const updateIds = /* @__PURE__ */ new Set();
964
+ for (const update of scene.update?.elements ?? []) {
965
+ const existing = elements.get(update.id)?.asset;
966
+ validatePatch(update, document, errors, warnings, scene.id, existing);
967
+ updateIds.add(update.id);
968
+ if (!elements.has(update.id)) {
969
+ errors.push(
970
+ issue("ELEMENT_NOT_PRESENT", `Element "${update.id}" is not present`, {
971
+ sceneId: scene.id,
972
+ elementId: update.id
973
+ })
974
+ );
975
+ }
976
+ }
977
+ for (const removal of scene.remove?.elements ?? []) {
978
+ validateRemoval(removal, errors, scene.id);
979
+ if (updateIds.has(removal.id)) {
980
+ errors.push(
981
+ issue("ELEMENT_DELTA_CONFLICT", `Element "${removal.id}" cannot be updated and removed in one scene`, {
982
+ sceneId: scene.id,
983
+ elementId: removal.id
984
+ })
985
+ );
986
+ }
987
+ if (!elements.has(removal.id)) {
988
+ errors.push(
989
+ issue("ELEMENT_NOT_PRESENT", `Element "${removal.id}" is not present`, {
990
+ sceneId: scene.id,
991
+ elementId: removal.id
992
+ })
993
+ );
994
+ }
995
+ }
996
+ const addedElementIds = /* @__PURE__ */ new Set();
997
+ for (const add of scene.add?.elements ?? []) {
998
+ validatePlacement(add, document, errors, warnings, scene.id);
999
+ if (addedElementIds.has(add.id)) {
1000
+ errors.push(
1001
+ issue("DUPLICATE_ELEMENT_ID", `Duplicate element "${add.id}"`, {
1002
+ sceneId: scene.id,
1003
+ elementId: add.id
1004
+ })
1005
+ );
1006
+ }
1007
+ addedElementIds.add(add.id);
1008
+ if (elements.has(add.id)) {
1009
+ errors.push(
1010
+ issue("ELEMENT_ALREADY_PRESENT", `Element "${add.id}" is already present`, {
1011
+ sceneId: scene.id,
1012
+ elementId: add.id
1013
+ })
1014
+ );
1015
+ }
1016
+ }
1017
+ const elementsForConnections = new Map(elements);
1018
+ for (const update of scene.update?.elements ?? []) {
1019
+ const existing = elementsForConnections.get(update.id);
1020
+ if (existing) {
1021
+ elementsForConnections.set(update.id, mergeElementPatch(existing, update));
1022
+ }
1023
+ }
1024
+ for (const add of scene.add?.elements ?? []) {
1025
+ elementsForConnections.set(add.id, normalizePlacement(document, add));
1026
+ }
1027
+ validateEndpointRemovalRule(scene, connectors, errors);
1028
+ const removedElementIds = new Set((scene.remove?.elements ?? []).map((removal) => removal.id));
1029
+ const connectorUpdateIds = /* @__PURE__ */ new Set();
1030
+ for (const update of scene.update?.connections ?? []) {
1031
+ validateConnectionRemovalLikeId(update.id, errors, scene.id);
1032
+ connectorUpdateIds.add(update.id);
1033
+ const existing = connectors.get(update.id);
1034
+ if (!existing) {
1035
+ errors.push(
1036
+ issue("CONNECTOR_NOT_PRESENT", `Connection "${update.id}" is not present`, {
1037
+ sceneId: scene.id,
1038
+ elementId: update.id
1039
+ })
1040
+ );
1041
+ validateConnectionPatch(update, document, errors, scene.id, elementsForConnections, documentElementIds);
1042
+ continue;
1043
+ }
1044
+ const merged = {
1045
+ ...existing,
1046
+ ...update,
1047
+ style: mergeStyle(existing.style, update.style)
1048
+ };
1049
+ validateConnectionPatch(merged, document, errors, scene.id, elementsForConnections, documentElementIds);
1050
+ validateEndpointsAgainstRemovals(merged, removedElementIds, errors, scene.id);
1051
+ }
1052
+ for (const removal of scene.remove?.connections ?? []) {
1053
+ validateConnectionRemoval(removal, errors, scene.id);
1054
+ if (connectorUpdateIds.has(removal.id)) {
1055
+ errors.push(
1056
+ issue("CONNECTOR_DELTA_CONFLICT", `Connection "${removal.id}" cannot be updated and removed in one scene`, {
1057
+ sceneId: scene.id,
1058
+ elementId: removal.id
1059
+ })
1060
+ );
1061
+ }
1062
+ if (!connectors.has(removal.id)) {
1063
+ errors.push(
1064
+ issue("CONNECTOR_NOT_PRESENT", `Connection "${removal.id}" is not present`, {
1065
+ sceneId: scene.id,
1066
+ elementId: removal.id
1067
+ })
1068
+ );
1069
+ }
1070
+ }
1071
+ const addedConnectorIds = /* @__PURE__ */ new Set();
1072
+ for (const add of scene.add?.connections ?? []) {
1073
+ validateConnectionPlacement(add, document, errors, scene.id, elementsForConnections, documentElementIds);
1074
+ validateEndpointsAgainstRemovals(add, removedElementIds, errors, scene.id);
1075
+ if (addedConnectorIds.has(add.id)) {
1076
+ errors.push(
1077
+ issue("DUPLICATE_CONNECTOR_ID", `Duplicate connection "${add.id}"`, {
1078
+ sceneId: scene.id,
1079
+ elementId: add.id
1080
+ })
1081
+ );
1082
+ }
1083
+ addedConnectorIds.add(add.id);
1084
+ if (connectors.has(add.id)) {
1085
+ errors.push(
1086
+ issue("CONNECTOR_ALREADY_PRESENT", `Connection "${add.id}" is already present`, {
1087
+ sceneId: scene.id,
1088
+ elementId: add.id
1089
+ })
1090
+ );
1091
+ }
1092
+ }
1093
+ validateCamera(scene.camera, scene.id, elementsForConnections, connectors, errors);
1094
+ for (const update of scene.update?.elements ?? []) {
1095
+ const existing = elements.get(update.id);
1096
+ if (existing) {
1097
+ elements.set(update.id, mergeElementPatch(existing, update));
1098
+ }
1099
+ }
1100
+ for (const add of scene.add?.elements ?? []) {
1101
+ elements.set(add.id, normalizePlacement(document, add));
1102
+ }
1103
+ for (const update of scene.update?.connections ?? []) {
1104
+ const existing = connectors.get(update.id);
1105
+ if (existing) {
1106
+ connectors.set(update.id, {
1107
+ ...existing,
1108
+ ...update,
1109
+ style: mergeStyle(existing.style, update.style)
1110
+ });
1111
+ }
1112
+ }
1113
+ for (const add of scene.add?.connections ?? []) {
1114
+ connectors.set(add.id, normalizeConnectionPlacement(document, add));
1115
+ }
1116
+ for (const removal of scene.remove?.connections ?? []) {
1117
+ connectors.delete(removal.id);
1118
+ }
1119
+ for (const removal of scene.remove?.elements ?? []) {
1120
+ elements.delete(removal.id);
1121
+ }
1122
+ }
1123
+ }
1124
+ function collectDocumentElementIds(document) {
1125
+ const ids = /* @__PURE__ */ new Set();
1126
+ for (const scene of document.scenes) {
1127
+ for (const element of scene.elements ?? []) ids.add(element.id);
1128
+ for (const element of scene.add?.elements ?? []) ids.add(element.id);
1129
+ }
1130
+ return ids;
1131
+ }
1132
+ function validateCamera(camera, sceneId, elements, connectors, errors) {
1133
+ if (!camera) return;
1134
+ const target = camera.target;
1135
+ const hasElement = "element" in target && target.element !== void 0;
1136
+ const hasArea = "area" in target && target.area !== void 0;
1137
+ const hasReset = "reset" in target && target.reset !== void 0;
1138
+ const targetCount = Number(hasElement) + Number(hasArea) + Number(hasReset);
1139
+ if (targetCount !== 1) {
1140
+ errors.push(issue("INVALID_CAMERA_TARGET", "Camera target must use exactly one target kind", { sceneId }));
1141
+ }
1142
+ if (hasElement) {
1143
+ if (!isValidIdentifier(target.element)) {
1144
+ errors.push(issue("INVALID_CAMERA_TARGET", "Camera element target must be kebab-case", { sceneId }));
1145
+ } else if (connectors.has(target.element)) {
1146
+ errors.push(issue("INVALID_CAMERA_TARGET", "Camera element target cannot reference a connector", { sceneId }));
1147
+ } else if (!elements.has(target.element)) {
1148
+ errors.push(
1149
+ issue("CAMERA_TARGET_NOT_FOUND", `Camera target "${target.element}" was not found`, {
1150
+ sceneId,
1151
+ elementId: target.element
1152
+ })
1153
+ );
1154
+ }
1155
+ }
1156
+ if (hasArea) {
1157
+ if (!isValidPosition(target.area.at) || !isValidPositiveTuple(target.area.size)) {
1158
+ errors.push(
1159
+ issue("INVALID_CAMERA_OPTIONS", "Camera area must use non-negative at and positive size", { sceneId })
1160
+ );
1161
+ }
1162
+ }
1163
+ if (hasReset && target.reset !== true) {
1164
+ errors.push(issue("INVALID_CAMERA_TARGET", "Camera reset target must be true", { sceneId }));
1165
+ }
1166
+ if (camera.padding !== void 0) {
1167
+ if (hasReset) {
1168
+ errors.push(issue("INVALID_CAMERA_OPTIONS", "Camera reset must not define padding", { sceneId }));
1169
+ }
1170
+ if (!Number.isFinite(camera.padding) || camera.padding < 0 || camera.padding > 2048) {
1171
+ errors.push(issue("INVALID_CAMERA_OPTIONS", "Camera padding is invalid", { sceneId }));
1172
+ }
1173
+ }
1174
+ if (camera.duration !== void 0 && (!Number.isInteger(camera.duration) || camera.duration < 0 || camera.duration > 1e4)) {
1175
+ errors.push(issue("INVALID_CAMERA_OPTIONS", "Camera duration is invalid", { sceneId }));
1176
+ }
1177
+ if (camera.easing !== void 0 && !VALID_CAMERA_EASINGS.has(camera.easing)) {
1178
+ errors.push(issue("INVALID_CAMERA_OPTIONS", "Camera easing is invalid", { sceneId }));
1179
+ }
1180
+ }
1181
+ function validateEndpointRemovalRule(scene, connectors, errors) {
1182
+ const removedElements = new Set((scene.remove?.elements ?? []).map((removal) => removal.id));
1183
+ if (removedElements.size === 0) return;
1184
+ const removedConnections = new Set((scene.remove?.connections ?? []).map((removal) => removal.id));
1185
+ const updatedConnections = new Set((scene.update?.connections ?? []).map((update) => update.id));
1186
+ for (const connection of connectors.values()) {
1187
+ if (removedConnections.has(connection.id) || updatedConnections.has(connection.id)) continue;
1188
+ const endpointIds = [connection.from?.element, connection.to?.element];
1189
+ if (endpointIds.some((id) => id !== void 0 && removedElements.has(id))) {
1190
+ errors.push(
1191
+ issue(
1192
+ "CONNECTION_ENDPOINT_REMOVED",
1193
+ `Connection "${connection.id}" references an element removed in the same scene`,
1194
+ { sceneId: scene.id, elementId: connection.id }
1195
+ )
1196
+ );
1197
+ }
1198
+ }
1199
+ }
1200
+ function validateEndpointsAgainstRemovals(connection, removedElementIds, errors, sceneId) {
1201
+ if (removedElementIds.size === 0) return;
1202
+ const endpointIds = [connection.from?.element, connection.to?.element];
1203
+ if (endpointIds.some((id) => id !== void 0 && removedElementIds.has(id))) {
1204
+ errors.push(
1205
+ issue(
1206
+ "CONNECTION_ENDPOINT_REMOVED",
1207
+ `Connection "${connection.id}" references an element removed in the same scene`,
1208
+ { sceneId, elementId: connection.id }
1209
+ )
1210
+ );
1211
+ }
1212
+ }
1213
+ function validateConnectionPlacement(connection, document, errors, sceneId, elements, documentElementIds) {
1214
+ validateConnectionCommon(connection, document, errors, sceneId, elements, documentElementIds);
1215
+ }
1216
+ function validateConnectionPatch(connection, document, errors, sceneId, elements, documentElementIds) {
1217
+ validateConnectionCommon(connection, document, errors, sceneId, elements, documentElementIds);
1218
+ }
1219
+ function validateConnectionCommon(connection, document, errors, sceneId, elements, documentElementIds) {
1220
+ validateConnectionRemovalLikeId(connection.id, errors, sceneId);
1221
+ if (documentElementIds.has(connection.id)) {
1222
+ errors.push(
1223
+ issue("DUPLICATE_SCENE_OBJECT_ID", `Connection "${connection.id}" collides with an element id`, {
1224
+ sceneId,
1225
+ elementId: connection.id
1226
+ })
1227
+ );
1228
+ }
1229
+ if (connection.layer !== void 0 && !declaredLayerNames(document).has(connection.layer)) {
1230
+ errors.push(
1231
+ issue("LAYER_NOT_FOUND", `Layer "${connection.layer}" is not declared`, {
1232
+ sceneId,
1233
+ elementId: connection.id,
1234
+ layerName: connection.layer
1235
+ })
1236
+ );
1237
+ }
1238
+ if (connection.enter !== void 0 && !VALID_ENTRY_ANIMATIONS.has(connection.enter)) {
1239
+ errors.push(
1240
+ issue("UNKNOWN_ANIMATION", `Unknown entry animation "${connection.enter}"`, {
1241
+ sceneId,
1242
+ elementId: connection.id
1243
+ })
1244
+ );
1245
+ }
1246
+ if (connection.exit !== void 0 && !VALID_EXIT_ANIMATIONS.has(connection.exit)) {
1247
+ errors.push(
1248
+ issue("UNKNOWN_ANIMATION", `Unknown exit animation "${connection.exit}"`, {
1249
+ sceneId,
1250
+ elementId: connection.id
1251
+ })
1252
+ );
1253
+ }
1254
+ validateAmbientWithSet(connection.ambient, VALID_CONNECTOR_AMBIENT_ANIMATIONS, errors, sceneId, connection.id);
1255
+ validateConnectionRouteSource(connection, errors, sceneId);
1256
+ validateConnectionEndpoints(connection, elements, errors, sceneId);
1257
+ validateConnectionRouting(connection, errors, sceneId);
1258
+ validateConnectorStyle(connection.style, errors, sceneId, connection.id);
1259
+ validateMessage(connection.message, errors, sceneId, connection.id);
1260
+ if (connection.start !== void 0 && !VALID_CONNECTOR_ENDPOINTS.has(connection.start)) {
1261
+ errors.push(
1262
+ issue("INVALID_CONNECTOR_ENDPOINT", "Invalid connector start endpoint", {
1263
+ sceneId,
1264
+ elementId: connection.id
1265
+ })
1266
+ );
1267
+ }
1268
+ if (connection.end !== void 0 && !VALID_CONNECTOR_ENDPOINTS.has(connection.end)) {
1269
+ errors.push(
1270
+ issue("INVALID_CONNECTOR_ENDPOINT", "Invalid connector end endpoint", {
1271
+ sceneId,
1272
+ elementId: connection.id
1273
+ })
1274
+ );
1275
+ }
1276
+ if (connection.direction !== void 0 && !VALID_CONNECTOR_DIRECTIONS.has(connection.direction)) {
1277
+ errors.push(
1278
+ issue("INVALID_CONNECTOR_DIRECTION", "Invalid connector direction", {
1279
+ sceneId,
1280
+ elementId: connection.id
1281
+ })
1282
+ );
1283
+ }
1284
+ }
1285
+ function validateConnectionRouteSource(connection, errors, sceneId) {
1286
+ const hasRoute = connection.route !== void 0;
1287
+ const hasEndpointRoute = connection.from !== void 0 || connection.to !== void 0;
1288
+ if (hasRoute === hasEndpointRoute || hasEndpointRoute && (!connection.from || !connection.to)) {
1289
+ errors.push(
1290
+ issue("INVALID_CONNECTOR_ROUTE", "Connection must use either route or both from and to", {
1291
+ sceneId,
1292
+ elementId: connection.id
1293
+ })
1294
+ );
1295
+ }
1296
+ if (connection.route !== void 0) {
1297
+ if (connection.route.length < 2 || connection.route.some(
1298
+ (point) => !isValidPosition(point) || point.some((coordinate) => !Number.isInteger(coordinate))
1299
+ )) {
1300
+ errors.push(
1301
+ issue("INVALID_CONNECTOR_ROUTE", "Manual connection route must contain at least two whole-grid points", {
1302
+ sceneId,
1303
+ elementId: connection.id
1304
+ })
1305
+ );
1306
+ } else if (!isGridAxisRoute(connection.route)) {
1307
+ errors.push(
1308
+ issue("INVALID_CONNECTOR_ROUTE", "Manual connection route segments must follow one grid axis at a time", {
1309
+ sceneId,
1310
+ elementId: connection.id
1311
+ })
1312
+ );
1313
+ }
1314
+ }
1315
+ }
1316
+ function validateConnectionEndpoints(connection, elements, errors, sceneId) {
1317
+ for (const key of ["from", "to"]) {
1318
+ const endpoint = connection[key];
1319
+ if (endpoint === void 0) continue;
1320
+ const hasElement = endpoint.element !== void 0;
1321
+ const hasAt = endpoint.at !== void 0;
1322
+ if (hasElement === hasAt) {
1323
+ errors.push(
1324
+ issue("INVALID_CONNECTOR_ROUTE", "Connection endpoint must use exactly one of element or at", {
1325
+ sceneId,
1326
+ elementId: connection.id
1327
+ })
1328
+ );
1329
+ }
1330
+ if (endpoint.element !== void 0 && !elements.has(endpoint.element)) {
1331
+ errors.push(
1332
+ issue("CONNECTOR_ENDPOINT_NOT_FOUND", `Connection endpoint "${endpoint.element}" was not found`, {
1333
+ sceneId,
1334
+ elementId: connection.id
1335
+ })
1336
+ );
1337
+ }
1338
+ if (endpoint.at !== void 0 && !isValidPosition(endpoint.at)) {
1339
+ errors.push(
1340
+ issue("INVALID_CONNECTOR_ROUTE", "Endpoint at must be non-negative", {
1341
+ sceneId,
1342
+ elementId: connection.id
1343
+ })
1344
+ );
1345
+ }
1346
+ if (endpoint.side !== void 0 && !VALID_CONNECTOR_SIDES.has(endpoint.side)) {
1347
+ errors.push(
1348
+ issue("INVALID_CONNECTOR_ROUTE", "Invalid endpoint side", {
1349
+ sceneId,
1350
+ elementId: connection.id
1351
+ })
1352
+ );
1353
+ }
1354
+ if (endpoint.offset !== void 0 && (!Number.isFinite(endpoint.offset) || endpoint.offset < -0.5 || endpoint.offset > 0.5)) {
1355
+ errors.push(
1356
+ issue("INVALID_CONNECTOR_ROUTE", "Invalid endpoint offset", {
1357
+ sceneId,
1358
+ elementId: connection.id
1359
+ })
1360
+ );
1361
+ }
1362
+ }
1363
+ }
1364
+ function validateConnectionRouting(connection, errors, sceneId) {
1365
+ const routing = connection.routing;
1366
+ if (routing === void 0) return;
1367
+ if (connection.route !== void 0) {
1368
+ errors.push(
1369
+ issue("INVALID_CONNECTOR_ROUTING", "Routing config is valid only with endpoint-routed connections", {
1370
+ sceneId,
1371
+ elementId: connection.id
1372
+ })
1373
+ );
1374
+ }
1375
+ if (routing.mode !== void 0 && !VALID_CONNECTOR_ROUTING_MODES.has(routing.mode)) {
1376
+ errors.push(
1377
+ issue("INVALID_CONNECTOR_ROUTING", "Invalid connector routing mode", {
1378
+ sceneId,
1379
+ elementId: connection.id
1380
+ })
1381
+ );
1382
+ }
1383
+ if (routing.avoid !== void 0) {
1384
+ const avoid = routing.avoid;
1385
+ if (!(avoid === "objects" || avoid === "none" || Array.isArray(avoid))) {
1386
+ errors.push(
1387
+ issue("INVALID_CONNECTOR_ROUTING", "Invalid connector routing avoid", {
1388
+ sceneId,
1389
+ elementId: connection.id
1390
+ })
1391
+ );
1392
+ }
1393
+ }
1394
+ for (const [name, value] of [
1395
+ ["clearance", routing.clearance],
1396
+ ["gridStep", routing.gridStep],
1397
+ ["maxBends", routing.maxBends]
1398
+ ]) {
1399
+ if (value !== void 0 && (!Number.isFinite(value) || value < 0)) {
1400
+ errors.push(
1401
+ issue("INVALID_CONNECTOR_ROUTING", `Invalid connector routing ${name}`, { sceneId, elementId: connection.id })
1402
+ );
1403
+ }
1404
+ }
1405
+ if (routing.prefer !== void 0 && !VALID_CONNECTOR_ROUTING_PREFERENCES.has(routing.prefer)) {
1406
+ errors.push(
1407
+ issue("INVALID_CONNECTOR_ROUTING", "Invalid connector routing preference", {
1408
+ sceneId,
1409
+ elementId: connection.id
1410
+ })
1411
+ );
1412
+ }
1413
+ }
1414
+ function validateConnectorStyle(style, errors, sceneId, connectionId) {
1415
+ if (style === void 0) return;
1416
+ if (style.pattern !== void 0 && !VALID_CONNECTOR_PATTERNS.has(style.pattern)) {
1417
+ errors.push(
1418
+ issue("INVALID_CONNECTOR_STYLE", "Invalid connector pattern", {
1419
+ sceneId,
1420
+ elementId: connectionId
1421
+ })
1422
+ );
1423
+ }
1424
+ if (style.variant !== void 0 && !VALID_CONNECTOR_VARIANTS.has(style.variant)) {
1425
+ errors.push(
1426
+ issue("INVALID_CONNECTOR_STYLE", "Invalid connector variant", {
1427
+ sceneId,
1428
+ elementId: connectionId
1429
+ })
1430
+ );
1431
+ }
1432
+ if (style.lane !== void 0 && !VALID_CONNECTOR_LANES.has(style.lane)) {
1433
+ errors.push(
1434
+ issue("INVALID_CONNECTOR_STYLE", "Invalid connector lane", {
1435
+ sceneId,
1436
+ elementId: connectionId
1437
+ })
1438
+ );
1439
+ }
1440
+ for (const [name, value] of [
1441
+ ["strokeWidth", style.strokeWidth],
1442
+ ["outlineWidth", style.outlineWidth],
1443
+ ["glowWidth", style.glowWidth]
1444
+ ]) {
1445
+ if (value !== void 0 && !isValidPositiveNumber(value)) {
1446
+ errors.push(
1447
+ issue("INVALID_CONNECTOR_STYLE", `Invalid connector ${name}`, {
1448
+ sceneId,
1449
+ elementId: connectionId
1450
+ })
1451
+ );
1452
+ }
1453
+ }
1454
+ if (style.opacity !== void 0 && (!Number.isFinite(style.opacity) || style.opacity < 0 || style.opacity > 1)) {
1455
+ errors.push(
1456
+ issue("INVALID_CONNECTOR_STYLE", "Connector opacity must be 0..1", {
1457
+ sceneId,
1458
+ elementId: connectionId
1459
+ })
1460
+ );
1461
+ }
1462
+ if (style.dash !== void 0 && (!isValidPositiveTuple(style.dash) || style.dash.some((part) => !Number.isFinite(part)))) {
1463
+ errors.push(
1464
+ issue("INVALID_CONNECTOR_STYLE", "Invalid connector dash", {
1465
+ sceneId,
1466
+ elementId: connectionId
1467
+ })
1468
+ );
1469
+ }
1470
+ if (style.cornerRadius !== void 0 && (!Number.isFinite(style.cornerRadius) || style.cornerRadius < 0)) {
1471
+ errors.push(
1472
+ issue("INVALID_CONNECTOR_STYLE", "Corner radius must be finite and non-negative", {
1473
+ sceneId,
1474
+ elementId: connectionId,
1475
+ field: "style.cornerRadius"
1476
+ })
1477
+ );
1478
+ }
1479
+ for (const token of [style.stroke, style.outline, style.glow]) {
1480
+ if (token !== void 0 && !isSafeTextStyleToken(token)) {
1481
+ errors.push(
1482
+ issue("INVALID_CONNECTOR_STYLE", "Connector color token is unsafe", {
1483
+ sceneId,
1484
+ elementId: connectionId
1485
+ })
1486
+ );
1487
+ }
1488
+ }
1489
+ }
1490
+ function validateConnectionRemoval(removal, errors, sceneId) {
1491
+ validateConnectionRemovalLikeId(removal.id, errors, sceneId);
1492
+ if (removal.exit !== void 0 && !VALID_EXIT_ANIMATIONS.has(removal.exit)) {
1493
+ errors.push(
1494
+ issue("UNKNOWN_ANIMATION", `Unknown exit animation "${removal.exit}"`, {
1495
+ sceneId,
1496
+ elementId: removal.id
1497
+ })
1498
+ );
1499
+ }
1500
+ }
1501
+ function validateConnectionRemovalLikeId(id, errors, sceneId) {
1502
+ if (!isValidIdentifier(id)) {
1503
+ errors.push(
1504
+ issue("INVALID_IDENTIFIER", `Connection "${id}" must be kebab-case`, {
1505
+ sceneId,
1506
+ elementId: id
1507
+ })
1508
+ );
1509
+ }
1510
+ }
1511
+ function mergeStyle(base, patch) {
1512
+ if (base === void 0) return patch;
1513
+ if (patch === void 0) return base;
1514
+ return { ...base, ...patch };
1515
+ }
1516
+ function mergeText(base, patch) {
1517
+ if (base === void 0) return patch;
1518
+ if (patch === void 0) return base;
1519
+ return { ...base, ...patch };
1520
+ }
1521
+ function mergePrimitive(base, patch) {
1522
+ if (base === void 0) return patch;
1523
+ if (patch === void 0) return base;
1524
+ const merged = {};
1525
+ if (base.rectangle || patch.rectangle) merged.rectangle = { ...base.rectangle ?? {}, ...patch.rectangle ?? {} };
1526
+ if (base.circle || patch.circle) merged.circle = { ...base.circle ?? {}, ...patch.circle ?? {} };
1527
+ if (base.polygon || patch.polygon) merged.polygon = { ...base.polygon ?? {}, ...patch.polygon ?? {} };
1528
+ if (base.line || patch.line) merged.line = { ...base.line ?? {}, ...patch.line ?? {} };
1529
+ return merged;
1530
+ }
1531
+ function mergeElementPatch(existing, patch) {
1532
+ return {
1533
+ ...existing,
1534
+ ...patch,
1535
+ text: mergeText(existing.text, patch.text),
1536
+ primitive: mergePrimitive(existing.primitive, patch.primitive)
1537
+ };
1538
+ }
1539
+ function validateWarnings(document, warnings) {
1540
+ const usedAssets = /* @__PURE__ */ new Set();
1541
+ const usedLayers = /* @__PURE__ */ new Set();
1542
+ const snapshots = resolveSceneSnapshots(document);
1543
+ if (document.header.floor?.asset && document.header.floor.visible !== false) {
1544
+ usedAssets.add(document.header.floor.asset);
1545
+ }
1546
+ const floorBounds = document.header.floor?.size ? {
1547
+ min: document.header.floor.origin ?? [0, 0],
1548
+ size: document.header.floor.size
1549
+ } : void 0;
1550
+ for (const snapshot of snapshots) {
1551
+ for (const element of snapshot.elements) {
1552
+ usedAssets.add(element.asset);
1553
+ if (isBuiltInAsset(element.asset)) {
1554
+ usedAssets.delete(element.asset);
1555
+ }
1556
+ usedLayers.add(element.layer);
1557
+ if (floorBounds) {
1558
+ const outside = [0, 1].some(
1559
+ (axis) => element.pos[axis] < floorBounds.min[axis] || element.pos[axis] + element.size > floorBounds.min[axis] + floorBounds.size[axis]
1560
+ );
1561
+ if (outside) {
1562
+ warnings.push(
1563
+ issue("ELEMENT_OUTSIDE_FLOOR", `Element "${element.id}" is outside floor bounds`, {
1564
+ sceneId: snapshot.id,
1565
+ elementId: element.id
1566
+ })
1567
+ );
1568
+ }
1569
+ }
1570
+ }
1571
+ for (const connector of snapshot.connectors) {
1572
+ usedLayers.add(connector.layer);
1573
+ if (floorBounds) {
1574
+ const outside = connector.route.some(
1575
+ (point) => [0, 1].some(
1576
+ (axis) => point[axis] < floorBounds.min[axis] || point[axis] > floorBounds.min[axis] + floorBounds.size[axis]
1577
+ )
1578
+ );
1579
+ if (outside) {
1580
+ warnings.push(
1581
+ issue("CONNECTOR_OUTSIDE_FLOOR", `Connection "${connector.id}" is outside floor bounds`, {
1582
+ sceneId: snapshot.id,
1583
+ elementId: connector.id
1584
+ })
1585
+ );
1586
+ }
1587
+ }
1588
+ }
1589
+ }
1590
+ for (const asset of document.header.assets) {
1591
+ const isUsed = isSpriteSheetAsset(asset) ? Object.keys(asset.sprites ?? {}).some((spriteId) => usedAssets.has(spriteId)) : usedAssets.has(asset.id);
1592
+ if (!isUsed) {
1593
+ warnings.push(
1594
+ issue("UNREFERENCED_ASSET", `Asset "${asset.id}" is not used`, {
1595
+ assetName: asset.id
1596
+ })
1597
+ );
1598
+ }
1599
+ }
1600
+ for (const layer of document.header.layers) {
1601
+ if (!usedLayers.has(layer.name) && layer.name !== defaultFloorLayer$1(document)) {
1602
+ warnings.push(
1603
+ issue("UNREFERENCED_LAYER", `Layer "${layer.name}" is not used`, {
1604
+ layerName: layer.name
1605
+ })
1606
+ );
1607
+ }
1608
+ }
1609
+ }
1610
+ function validateScene(document) {
1611
+ const errors = [];
1612
+ const warnings = [];
1613
+ validateHeader(document, errors);
1614
+ validateTimelineShape(document, errors);
1615
+ validateSceneObjectDeltas(document, errors, warnings);
1616
+ if (errors.length === 0) {
1617
+ validateWarnings(document, warnings);
1618
+ }
1619
+ return {
1620
+ errors,
1621
+ warnings,
1622
+ isValid: errors.length === 0
1623
+ };
1624
+ }
1625
+ function resolveSceneSnapshots(document) {
1626
+ const progress = deriveProgresses(document.scenes);
1627
+ const currentElements = /* @__PURE__ */ new Map();
1628
+ const currentConnectors = /* @__PURE__ */ new Map();
1629
+ const snapshots = [];
1630
+ for (const [index, scene] of document.scenes.entries()) {
1631
+ const exiting = [];
1632
+ const exitingConnectors = [];
1633
+ if (index === 0) {
1634
+ for (const element of scene.elements ?? []) {
1635
+ currentElements.set(element.id, normalizePlacement(document, element));
1636
+ }
1637
+ for (const connection of scene.connections ?? []) {
1638
+ currentConnectors.set(connection.id, normalizeConnectionPlacement(document, connection));
1639
+ }
1640
+ } else {
1641
+ for (const patch of scene.update?.elements ?? []) {
1642
+ const existing = currentElements.get(patch.id);
1643
+ if (existing) {
1644
+ currentElements.set(patch.id, mergeElementPatch(existing, patch));
1645
+ }
1646
+ }
1647
+ for (const element of scene.add?.elements ?? []) {
1648
+ currentElements.set(element.id, normalizePlacement(document, element));
1649
+ }
1650
+ for (const patch of scene.update?.connections ?? []) {
1651
+ const existing = currentConnectors.get(patch.id);
1652
+ if (existing) {
1653
+ currentConnectors.set(patch.id, {
1654
+ ...existing,
1655
+ ...patch,
1656
+ style: mergeStyle(existing.style, patch.style)
1657
+ });
1658
+ }
1659
+ }
1660
+ for (const connection of scene.add?.connections ?? []) {
1661
+ currentConnectors.set(connection.id, normalizeConnectionPlacement(document, connection));
1662
+ }
1663
+ for (const removal of scene.remove?.elements ?? []) {
1664
+ const existing = currentElements.get(removal.id);
1665
+ if (existing) {
1666
+ exiting.push(toRuntimeState(existing, "exiting", removal.exit));
1667
+ }
1668
+ }
1669
+ for (const removal of scene.remove?.connections ?? []) {
1670
+ const existing = currentConnectors.get(removal.id);
1671
+ if (existing) {
1672
+ exitingConnectors.push(toRuntimeConnectorState(document, existing, currentElements, "exiting", removal.exit));
1673
+ }
1674
+ }
1675
+ }
1676
+ const addedIds = new Set((scene.add?.elements ?? []).map((element) => element.id));
1677
+ const addedConnectorIds = new Set((scene.add?.connections ?? []).map((connection) => connection.id));
1678
+ const snapshotElements = Array.from(currentElements.values()).map(
1679
+ (element) => toRuntimeState(element, index > 0 && addedIds.has(element.id) ? "entering" : "present")
1680
+ );
1681
+ const snapshotConnectors = Array.from(currentConnectors.values()).map(
1682
+ (connection) => toRuntimeConnectorState(
1683
+ document,
1684
+ connection,
1685
+ currentElements,
1686
+ index > 0 && addedConnectorIds.has(connection.id) ? "entering" : "present"
1687
+ )
1688
+ );
1689
+ for (const removed of exiting) {
1690
+ const existingIndex = snapshotElements.findIndex((element) => element.id === removed.id);
1691
+ if (existingIndex >= 0) {
1692
+ snapshotElements[existingIndex] = removed;
1693
+ } else {
1694
+ snapshotElements.push(removed);
1695
+ }
1696
+ }
1697
+ for (const removed of exitingConnectors) {
1698
+ const existingIndex = snapshotConnectors.findIndex((connector) => connector.id === removed.id);
1699
+ if (existingIndex >= 0) {
1700
+ snapshotConnectors[existingIndex] = removed;
1701
+ } else {
1702
+ snapshotConnectors.push(removed);
1703
+ }
1704
+ }
1705
+ snapshots.push({
1706
+ id: scene.id,
1707
+ progress: progress[index],
1708
+ elements: snapshotElements,
1709
+ connectors: snapshotConnectors,
1710
+ ...scene.camera ? { camera: normalizeCamera(scene.camera) } : {}
1711
+ });
1712
+ for (const removal of scene.remove?.connections ?? []) {
1713
+ currentConnectors.delete(removal.id);
1714
+ }
1715
+ for (const removal of scene.remove?.elements ?? []) {
1716
+ currentElements.delete(removal.id);
1717
+ }
1718
+ }
1719
+ return snapshots;
1720
+ }
1721
+ function normalizeCamera(camera) {
1722
+ const normalized = "element" in camera.target && camera.target.element !== void 0 ? {
1723
+ target: { type: "element", id: camera.target.element },
1724
+ padding: camera.padding ?? 32
1725
+ } : "area" in camera.target && camera.target.area !== void 0 ? {
1726
+ target: { type: "area", at: camera.target.area.at, size: camera.target.area.size },
1727
+ padding: camera.padding ?? 32
1728
+ } : {
1729
+ target: { type: "reset" }
1730
+ };
1731
+ if (camera.duration !== void 0) normalized.duration = camera.duration;
1732
+ if (camera.easing !== void 0) normalized.easing = camera.easing;
1733
+ return normalized;
1734
+ }
1735
+ function deriveProgresses(scenes) {
1736
+ if (scenes.length <= 1) {
1737
+ return scenes.map(() => 0);
1738
+ }
1739
+ return scenes.map((_, index) => index / (scenes.length - 1));
1740
+ }
1741
+ function normalizePlacement(document, element) {
1742
+ return {
1743
+ id: element.id,
1744
+ asset: element.asset,
1745
+ at: element.at,
1746
+ size: element.size,
1747
+ layer: element.layer ?? defaultElementLayer(document),
1748
+ enter: element.enter,
1749
+ exit: element.exit,
1750
+ ambient: element.ambient,
1751
+ text: element.text,
1752
+ primitive: element.primitive,
1753
+ activity: element.activity
1754
+ };
1755
+ }
1756
+ function normalizeConnectionPlacement(document, connection) {
1757
+ return {
1758
+ id: connection.id,
1759
+ route: connection.route,
1760
+ from: connection.from,
1761
+ to: connection.to,
1762
+ routing: connection.routing,
1763
+ layer: connection.layer ?? defaultConnectorLayer(document),
1764
+ style: connection.style,
1765
+ start: connection.start,
1766
+ end: connection.end,
1767
+ direction: connection.direction,
1768
+ enter: connection.enter,
1769
+ exit: connection.exit,
1770
+ ambient: connection.ambient,
1771
+ message: connection.message
1772
+ };
1773
+ }
1774
+ function toRuntimeState(element, presence, exit) {
1775
+ return {
1776
+ id: element.id,
1777
+ asset: element.asset,
1778
+ pos: element.at,
1779
+ size: element.size ?? 1,
1780
+ layer: element.layer ?? "",
1781
+ presence,
1782
+ enter: presence === "entering" ? element.enter ?? "fade-in" : element.enter,
1783
+ exit: presence === "exiting" ? exit ?? element.exit ?? "fade-out" : exit ?? element.exit,
1784
+ ambient: element.ambient,
1785
+ text: element.text,
1786
+ primitive: element.primitive,
1787
+ activity: element.activity
1788
+ };
1789
+ }
1790
+ function toRuntimeConnectorState(document, connection, elements, presence, exit) {
1791
+ return {
1792
+ id: connection.id,
1793
+ route: resolveConnectorRoute(connection, elements),
1794
+ layer: connection.layer ?? defaultConnectorLayer(document),
1795
+ presence,
1796
+ style: resolveConnectorStyle(connection.style),
1797
+ start: connection.start ?? "none",
1798
+ end: connection.end ?? "arrow",
1799
+ direction: connection.direction ?? "route",
1800
+ enter: presence === "entering" ? connection.enter ?? "fade-in" : connection.enter,
1801
+ exit: presence === "exiting" ? exit ?? connection.exit ?? "fade-out" : exit ?? connection.exit,
1802
+ ambient: connection.ambient,
1803
+ message: connection.message
1804
+ };
1805
+ }
1806
+ function resolveConnectorStyle(style) {
1807
+ const variant = style?.variant ?? "line";
1808
+ const pattern = style?.pattern ?? "solid";
1809
+ const resolved = {
1810
+ variant,
1811
+ pattern,
1812
+ stroke: style?.stroke ?? "#2563eb",
1813
+ strokeWidth: style?.strokeWidth ?? (variant === "road" ? 14 : variant === "beam" ? 6 : 3),
1814
+ opacity: style?.opacity ?? 1,
1815
+ outlineWidth: style?.outlineWidth ?? (variant === "road" || variant === "beam" ? 2 : 0),
1816
+ lane: style?.lane ?? "none"
1817
+ };
1818
+ if (style?.cornerRadius !== void 0) resolved.cornerRadius = style.cornerRadius;
1819
+ if (style?.glow !== void 0) resolved.glow = style.glow;
1820
+ if (style?.glowWidth !== void 0) resolved.glowWidth = style.glowWidth;
1821
+ const dash = style?.dash ?? defaultDash(pattern);
1822
+ if (dash) {
1823
+ resolved.dash = dash;
1824
+ }
1825
+ const outline = style?.outline ?? (variant === "road" ? "#ffffff" : variant === "beam" ? "#263746" : void 0);
1826
+ if (outline) {
1827
+ resolved.outline = outline;
1828
+ }
1829
+ return resolved;
1830
+ }
1831
+ function defaultDash(pattern) {
1832
+ if (pattern === "dashed") return [12, 8];
1833
+ if (pattern === "dotted") return [0, 8];
1834
+ return void 0;
1835
+ }
1836
+ function resolveConnectorRoute(connection, elements) {
1837
+ if (connection.route) return connection.route;
1838
+ if (!connection.from || !connection.to) return [];
1839
+ const routing = connection.routing ?? {};
1840
+ const avoid = routing.avoid ?? "objects";
1841
+ const clearance = routing.clearance ?? 0.5;
1842
+ const obstacles = avoid === "none" ? [] : collectObstacles(elements, connection.from, connection.to, avoid, clearance);
1843
+ const starts = endpointCandidates(connection.from, elements);
1844
+ const ends = endpointCandidates(connection.to, elements);
1845
+ let best;
1846
+ let bestScore = Number.POSITIVE_INFINITY;
1847
+ for (const start of starts) {
1848
+ for (const end of ends) {
1849
+ const route = routeBetween(start, end, obstacles, routing.mode ?? "orthogonal", clearance);
1850
+ const score = routeScore(route, obstacles);
1851
+ if (score < bestScore) {
1852
+ best = route;
1853
+ bestScore = score;
1854
+ }
1855
+ }
1856
+ }
1857
+ return simplifyRoute(best ?? [starts[0]?.point ?? [0, 0], ends[0]?.point ?? [0, 0]]);
1858
+ }
1859
+ function endpointCandidates(endpoint, elements) {
1860
+ if (endpoint.at) return [{ point: endpoint.at, sideRank: 0 }];
1861
+ const element = endpoint.element ? elements.get(endpoint.element) : void 0;
1862
+ if (!element) return [{ point: [0, 0], sideRank: 0 }];
1863
+ const sides = endpoint.side && endpoint.side !== "auto" ? [normalizeSide(endpoint.side)] : ["right", "left", "bottom", "top"];
1864
+ return sides.map((side, index) => ({
1865
+ point: portForSide(element, side, endpoint.offset ?? 0),
1866
+ normal: normalForSide(side),
1867
+ sideRank: index
1868
+ }));
1869
+ }
1870
+ function normalizeSide(side) {
1871
+ if (side === "front") return "bottom";
1872
+ if (side === "back") return "top";
1873
+ if (side === "auto") return "right";
1874
+ return side;
1875
+ }
1876
+ function portForSide(element, side, offset) {
1877
+ const x = element.at[0];
1878
+ const y = element.at[1];
1879
+ const size = element.size ?? 1;
1880
+ if (side === "top") return [x + size * (0.5 + offset), y];
1881
+ if (side === "right") return [x + size, y + size * (0.5 + offset)];
1882
+ if (side === "bottom") return [x + size * (0.5 - offset), y + size];
1883
+ return [x, y + size * (0.5 - offset)];
1884
+ }
1885
+ function normalForSide(side) {
1886
+ if (side === "top") return [0, -1];
1887
+ if (side === "right") return [1, 0];
1888
+ if (side === "bottom") return [0, 1];
1889
+ return [-1, 0];
1890
+ }
1891
+ function collectObstacles(elements, from, to, avoid, clearance) {
1892
+ const endpointIds = new Set([from.element, to.element].filter(Boolean));
1893
+ const avoidIds = Array.isArray(avoid) ? new Set(avoid) : void 0;
1894
+ const obstacles = [];
1895
+ for (const element of elements.values()) {
1896
+ if (endpointIds.has(element.id) || element.asset === BUILT_IN_TEXT_ASSET_ID$1) {
1897
+ continue;
1898
+ }
1899
+ if (avoidIds && !avoidIds.has(element.id)) continue;
1900
+ const size = element.size ?? 1;
1901
+ obstacles.push({
1902
+ minX: element.at[0] - clearance,
1903
+ minY: element.at[1] - clearance,
1904
+ maxX: element.at[0] + size + clearance,
1905
+ maxY: element.at[1] + size + clearance
1906
+ });
1907
+ }
1908
+ return obstacles;
1909
+ }
1910
+ function routeBetween(start, end, obstacles, mode = "orthogonal", clearance = 0.5) {
1911
+ const startExit = offsetPort(start, clearance);
1912
+ const endEntry = offsetPort(end, clearance);
1913
+ const direct = [startExit, endEntry];
1914
+ if (mode === "straight") return direct;
1915
+ if (isGridAxisRoute(direct) && !routeIntersectsObstacles(direct, obstacles)) {
1916
+ return withEndpointPorts(start, end, direct);
1917
+ }
1918
+ const candidates = [
1919
+ [startExit, [endEntry[0], startExit[1]], endEntry],
1920
+ [startExit, [startExit[0], endEntry[1]], endEntry]
1921
+ ];
1922
+ const blocking = obstacles.find((obstacle) => segmentIntersectsRect(startExit, endEntry, obstacle));
1923
+ if (blocking) {
1924
+ const leftX = Math.max(0, blocking.minX);
1925
+ const rightX = Math.max(0, blocking.maxX);
1926
+ const topY = Math.max(0, blocking.minY);
1927
+ const bottomY = Math.max(0, blocking.maxY);
1928
+ candidates.push(
1929
+ [startExit, [leftX, startExit[1]], [leftX, endEntry[1]], endEntry],
1930
+ [startExit, [rightX, startExit[1]], [rightX, endEntry[1]], endEntry],
1931
+ [startExit, [startExit[0], topY], [endEntry[0], topY], endEntry],
1932
+ [startExit, [startExit[0], bottomY], [endEntry[0], bottomY], endEntry]
1933
+ );
1934
+ }
1935
+ const route = candidates.map((route2) => simplifyRoute(route2)).sort((a, b) => routeScore(a, obstacles) - routeScore(b, obstacles) || routeLength(a) - routeLength(b))[0] ?? direct;
1936
+ return withEndpointPorts(start, end, route);
1937
+ }
1938
+ function offsetPort(endpoint, clearance) {
1939
+ if (!endpoint.normal || clearance <= 0) return endpoint.point;
1940
+ return [endpoint.point[0] + endpoint.normal[0] * clearance, endpoint.point[1] + endpoint.normal[1] * clearance];
1941
+ }
1942
+ function withEndpointPorts(start, end, route) {
1943
+ return simplifyRoute([start.point, ...route, end.point]);
1944
+ }
1945
+ function routeScore(route, obstacles) {
1946
+ const intersections = routeIntersectsObstacles(route, obstacles) ? 1e3 : 0;
1947
+ return intersections + bendCount(route) * 10 + routeLength(route);
1948
+ }
1949
+ function routeLength(route) {
1950
+ let length = 0;
1951
+ for (let index = 1; index < route.length; index += 1) {
1952
+ length += Math.abs(route[index][0] - route[index - 1][0]) + Math.abs(route[index][1] - route[index - 1][1]);
1953
+ }
1954
+ return length;
1955
+ }
1956
+ function bendCount(route) {
1957
+ let bends = 0;
1958
+ for (let index = 2; index < route.length; index += 1) {
1959
+ const a = route[index - 2];
1960
+ const b = route[index - 1];
1961
+ const c = route[index];
1962
+ if ((b[0] - a[0]) * (c[1] - b[1]) !== (b[1] - a[1]) * (c[0] - b[0])) {
1963
+ bends += 1;
1964
+ }
1965
+ }
1966
+ return bends;
1967
+ }
1968
+ function routeIntersectsObstacles(route, obstacles) {
1969
+ for (let index = 1; index < route.length; index += 1) {
1970
+ if (obstacles.some((obstacle) => segmentIntersectsRect(route[index - 1], route[index], obstacle))) {
1971
+ return true;
1972
+ }
1973
+ }
1974
+ return false;
1975
+ }
1976
+ function segmentIntersectsRect(a, b, rect) {
1977
+ if (a[0] < rect.minX && b[0] < rect.minX || a[0] > rect.maxX && b[0] > rect.maxX || a[1] < rect.minY && b[1] < rect.minY || a[1] > rect.maxY && b[1] > rect.maxY) {
1978
+ return false;
1979
+ }
1980
+ if (pointInsideRect(a, rect) || pointInsideRect(b, rect)) return true;
1981
+ const corners = [
1982
+ [rect.minX, rect.minY],
1983
+ [rect.maxX, rect.minY],
1984
+ [rect.maxX, rect.maxY],
1985
+ [rect.minX, rect.maxY]
1986
+ ];
1987
+ return [
1988
+ [corners[0], corners[1]],
1989
+ [corners[1], corners[2]],
1990
+ [corners[2], corners[3]],
1991
+ [corners[3], corners[0]]
1992
+ ].some(([c, d]) => segmentsIntersect(a, b, c, d));
1993
+ }
1994
+ function pointInsideRect(point, rect) {
1995
+ return point[0] > rect.minX && point[0] < rect.maxX && point[1] > rect.minY && point[1] < rect.maxY;
1996
+ }
1997
+ function segmentsIntersect(a, b, c, d) {
1998
+ const ccw = (p1, p2, p3) => (p3[1] - p1[1]) * (p2[0] - p1[0]) > (p2[1] - p1[1]) * (p3[0] - p1[0]);
1999
+ return ccw(a, c, d) !== ccw(b, c, d) && ccw(a, b, c) !== ccw(a, b, d);
2000
+ }
2001
+ function simplifyRoute(route) {
2002
+ const simplified = [];
2003
+ for (const point of route) {
2004
+ const previous = simplified[simplified.length - 1];
2005
+ if (previous && previous[0] === point[0] && previous[1] === point[1]) {
2006
+ continue;
2007
+ }
2008
+ simplified.push(point);
2009
+ while (simplified.length >= 3) {
2010
+ const a = simplified[simplified.length - 3];
2011
+ const b = simplified[simplified.length - 2];
2012
+ const c = simplified[simplified.length - 1];
2013
+ if ((b[0] - a[0]) * (c[1] - b[1]) === (b[1] - a[1]) * (c[0] - b[0])) {
2014
+ simplified.splice(simplified.length - 2, 1);
2015
+ } else {
2016
+ break;
2017
+ }
2018
+ }
2019
+ }
2020
+ return simplified;
2021
+ }
2022
+ function isGridAxisRoute(route) {
2023
+ for (let index = 1; index < route.length; index += 1) {
2024
+ const previous = route[index - 1];
2025
+ const current = route[index];
2026
+ if (previous[0] !== current[0] && previous[1] !== current[1]) {
2027
+ return false;
2028
+ }
2029
+ }
2030
+ return true;
2031
+ }
2032
+
2033
+ const DEFAULT_VERSION = "0.6.0";
2034
+ const RUNTIME_BUNDLE_FORMAT = "isostate-runtime-bundle";
2035
+ const BUILT_IN_TEXT_ASSET_ID = "text";
2036
+ const BUILT_IN_PRIMITIVE_ASSET_IDS = /* @__PURE__ */ new Set(["rectangle", "circle", "polygon", "line"]);
2037
+ function compileScene(document, options = {}) {
2038
+ const { version = DEFAULT_VERSION } = options;
2039
+ const scenes = resolveSceneSnapshots(document).map((scene) => normalizeValue(scene));
2040
+ const bundleWithoutDigest = {
2041
+ _format: RUNTIME_BUNDLE_FORMAT,
2042
+ _version: version,
2043
+ grid: { cellSize: document.header.grid?.cellSize ?? 64 },
2044
+ floor: compileFloor(document, scenes),
2045
+ layout: compileLayout(),
2046
+ theme: document.header.theme ?? "light",
2047
+ layers: compileLayers(document.header.layers),
2048
+ scenes
2049
+ };
2050
+ if (document.header.className) {
2051
+ bundleWithoutDigest.className = document.header.className;
2052
+ }
2053
+ const assets = compileExternalAssets(document, scenes);
2054
+ if (Object.keys(assets).length > 0) {
2055
+ bundleWithoutDigest.assets = assets;
2056
+ }
2057
+ return {
2058
+ ...bundleWithoutDigest,
2059
+ _digest: digestBundle(bundleWithoutDigest)
2060
+ };
2061
+ }
2062
+ function compileExternalAssets(document, scenes) {
2063
+ const assets = {};
2064
+ for (const name of uniqueReferencedAssetNames(document, scenes)) {
2065
+ const sprite = resolveSpriteAsset(document, name);
2066
+ if (sprite) {
2067
+ const url2 = resolveAssetUrl(document, sprite.sheet.id);
2068
+ if (!url2) {
2069
+ throw new ValidationErrorClass(
2070
+ "ASSET_URL_REQUIRED",
2071
+ `Sprite sheet "${sprite.sheet.id}" must resolve through header.assetBaseUrl`,
2072
+ {
2073
+ asset: sprite.sheet.id
2074
+ }
2075
+ );
2076
+ }
2077
+ assets[name] = normalizeValue({
2078
+ url: url2,
2079
+ sprite: sprite.compiled,
2080
+ anchor: sprite.anchor
2081
+ });
2082
+ continue;
2083
+ }
2084
+ const url = resolveAssetUrl(document, name);
2085
+ if (!url) {
2086
+ throw new ValidationErrorClass("ASSET_URL_REQUIRED", `Asset "${name}" must resolve through header.assetBaseUrl`, {
2087
+ asset: name
2088
+ });
2089
+ }
2090
+ const entry = document.header.assets.find((asset) => asset.id === name);
2091
+ assets[name] = normalizeValue({
2092
+ url,
2093
+ ...entry?.anchor ? { anchor: entry.anchor } : {}
2094
+ });
2095
+ }
2096
+ return assets;
2097
+ }
2098
+ function toJs(bundle, options = {}) {
2099
+ const { minify = true } = options;
2100
+ const json = canonicalStringify(bundle, minify ? void 0 : 2);
2101
+ return `export default ${json};`;
2102
+ }
2103
+ function toJson(bundle) {
2104
+ return canonicalStringify(bundle, 2);
2105
+ }
2106
+ function fromJs(moduleString) {
2107
+ if (!moduleString.startsWith("export default ") || !moduleString.endsWith(";")) {
2108
+ throw new ValidationErrorClass("INVALID_RUNTIME_BUNDLE_MODULE", "Invalid JS module: expected exact default export");
2109
+ }
2110
+ const jsonString = moduleString.slice("export default ".length, -1);
2111
+ const bundle = parseRuntimeBundleJson(jsonString, "INVALID_RUNTIME_BUNDLE_MODULE");
2112
+ const minify = !jsonString.includes("\n");
2113
+ if (moduleString !== toJs(bundle, { minify })) {
2114
+ throw new ValidationErrorClass("INVALID_RUNTIME_BUNDLE_MODULE", "Invalid JS module: non-canonical bundle export");
2115
+ }
2116
+ return bundle;
2117
+ }
2118
+ function fromJson(jsonString) {
2119
+ const bundle = parseRuntimeBundleJson(jsonString, "INVALID_RUNTIME_BUNDLE_JSON");
2120
+ if (jsonString !== toJson(bundle)) {
2121
+ throw new ValidationErrorClass("INVALID_RUNTIME_BUNDLE_JSON", "Invalid JSON bundle: non-canonical serialization");
2122
+ }
2123
+ return bundle;
2124
+ }
2125
+ function parseRuntimeBundleJson(jsonString, code) {
2126
+ try {
2127
+ return JSON.parse(jsonString);
2128
+ } catch {
2129
+ throw new ValidationErrorClass(code, "Invalid runtime bundle: expected valid JSON");
2130
+ }
2131
+ }
2132
+ function compileFloor(document, scenes) {
2133
+ const floor = document.header.floor ?? {};
2134
+ const origin = floor.origin ?? [0, 0];
2135
+ const compiled = {
2136
+ size: floor.size ?? deriveFloorSize(scenes, origin),
2137
+ origin,
2138
+ visible: floor.visible ?? true,
2139
+ layer: floor.layer ?? defaultFloorLayer(document)
2140
+ };
2141
+ if (floor.asset !== void 0) {
2142
+ compiled.asset = floor.asset;
2143
+ }
2144
+ return normalizeValue(compiled);
2145
+ }
2146
+ function deriveFloorSize(scenes, origin) {
2147
+ let maxX = origin[0] + 1;
2148
+ let maxY = origin[1] + 1;
2149
+ for (const scene of scenes) {
2150
+ for (const element of scene.elements) {
2151
+ if (element.presence === "removed") continue;
2152
+ maxX = Math.max(maxX, element.pos[0] + element.size);
2153
+ maxY = Math.max(maxY, element.pos[1] + element.size);
2154
+ }
2155
+ for (const connector of scene.connectors) {
2156
+ if (connector.presence === "removed") continue;
2157
+ for (const point of connector.route) {
2158
+ maxX = Math.max(maxX, point[0]);
2159
+ maxY = Math.max(maxY, point[1]);
2160
+ }
2161
+ }
2162
+ }
2163
+ return [Math.max(1, Math.ceil(maxX - origin[0])), Math.max(1, Math.ceil(maxY - origin[1]))];
2164
+ }
2165
+ function compileLayout() {
2166
+ return normalizeValue({
2167
+ fit: "contain",
2168
+ align: [0.5, 0.5],
2169
+ padding: { x: 64, y: 64 },
2170
+ bounds: "union"
2171
+ });
2172
+ }
2173
+ function compileLayers(layers) {
2174
+ return layers.map((layer, index) => ({
2175
+ name: layer.name,
2176
+ order: layer.order ?? index
2177
+ })).sort((a, b) => a.order - b.order || compareCodePointOrder(a.name, b.name));
2178
+ }
2179
+ function compareCodePointOrder(a, b) {
2180
+ return a < b ? -1 : a > b ? 1 : 0;
2181
+ }
2182
+ function resolveAssetUrl(document, assetId) {
2183
+ const base = document.header.assetBaseUrl;
2184
+ const entry = document.header.assets.find((asset) => asset.id === assetId);
2185
+ if (!base || !entry) return void 0;
2186
+ const path = entry.path ?? entry.id;
2187
+ const file = "type" in entry && entry.type === "sprite-sheet" ? path : path.endsWith(".svg") ? path : `${path}.svg`;
2188
+ return `${base.replace(/\/+$/, "")}/${file.replace(/^\/+/, "")}`;
2189
+ }
2190
+ function resolveSpriteAsset(document, spriteId) {
2191
+ for (const asset of document.header.assets) {
2192
+ if (!("type" in asset) || asset.type !== "sprite-sheet") continue;
2193
+ const definition = asset.sprites[spriteId];
2194
+ if (!definition) continue;
2195
+ const rect = compileSpriteRect(definition, asset.tileSize);
2196
+ if (!rect) {
2197
+ throw new ValidationErrorClass("INVALID_SPRITE_DEFINITION", `Sprite "${spriteId}" cannot be compiled`, {
2198
+ asset: spriteId
2199
+ });
2200
+ }
2201
+ const anchor = !Array.isArray(definition) && definition.anchor ? definition.anchor : asset.anchor ?? [0.5, 1];
2202
+ return {
2203
+ sheet: asset,
2204
+ compiled: {
2205
+ sheetSize: asset.sheetSize,
2206
+ rect
2207
+ },
2208
+ anchor
2209
+ };
2210
+ }
2211
+ return void 0;
2212
+ }
2213
+ function compileSpriteRect(sprite, tileSize) {
2214
+ if (Array.isArray(sprite)) {
2215
+ if (!tileSize) return void 0;
2216
+ return [sprite[0] * tileSize[0], sprite[1] * tileSize[1], tileSize[0], tileSize[1]];
2217
+ }
2218
+ if (sprite.rect) return sprite.rect;
2219
+ if (sprite.at && tileSize) {
2220
+ return [sprite.at[0] * tileSize[0], sprite.at[1] * tileSize[1], tileSize[0], tileSize[1]];
2221
+ }
2222
+ return void 0;
2223
+ }
2224
+ function uniqueReferencedAssetNames(document, scenes) {
2225
+ const names = /* @__PURE__ */ new Set();
2226
+ if (document.header.floor?.asset && document.header.floor.visible !== false) {
2227
+ names.add(document.header.floor.asset);
2228
+ }
2229
+ for (const scene of scenes) {
2230
+ for (const element of scene.elements) {
2231
+ if (!isBuiltInGeneratedAsset(element.asset)) {
2232
+ names.add(element.asset);
2233
+ }
2234
+ }
2235
+ }
2236
+ return Array.from(names).sort();
2237
+ }
2238
+ function isBuiltInGeneratedAsset(assetId) {
2239
+ return assetId === BUILT_IN_TEXT_ASSET_ID || BUILT_IN_PRIMITIVE_ASSET_IDS.has(assetId);
2240
+ }
2241
+ function defaultFloorLayer(document) {
2242
+ const ground = document.header.layers.find((layer) => layer.name === "ground");
2243
+ return ground?.name ?? document.header.layers[0]?.name ?? "";
2244
+ }
2245
+ function digestBundle(bundle) {
2246
+ return sha256(canonicalStringify(bundle));
2247
+ }
2248
+ function canonicalStringify(value, space) {
2249
+ const normalized = normalizeValue(value);
2250
+ return JSON.stringify(normalized, null, space);
2251
+ }
2252
+ function normalizeValue(value) {
2253
+ if (Array.isArray(value)) {
2254
+ return value.map((item) => item === void 0 ? null : normalizeValue(item));
2255
+ }
2256
+ if (!isPlainObject(value)) return value;
2257
+ const normalized = {};
2258
+ for (const key of Object.keys(value).sort()) {
2259
+ const child = value[key];
2260
+ if (child !== void 0) {
2261
+ normalized[key] = normalizeValue(child);
2262
+ }
2263
+ }
2264
+ return normalized;
2265
+ }
2266
+ function isPlainObject(value) {
2267
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2268
+ }
2269
+
2270
+ const IDENTIFIER_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
2271
+ const ASSET_PATH_PATTERN = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-]*)*(?:\.svg)?$/;
2272
+ const SPRITE_SHEET_PATH_PATTERN = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-]*)*\.(?:png|webp|jpe?g|svg)$/;
2273
+ function fail(code, message) {
2274
+ throw new ParseError(code, message, { line: 0, column: 0 });
2275
+ }
2276
+ function isObject(value) {
2277
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2278
+ }
2279
+ function assertKnownFields(obj, allowed, context) {
2280
+ for (const field of Object.keys(obj)) {
2281
+ if (!allowed.has(field)) {
2282
+ fail("UNKNOWN_FIELD", `Unknown field "${field}" in ${context}`);
2283
+ }
2284
+ }
2285
+ }
2286
+ function requireObject(value, context) {
2287
+ if (!isObject(value)) {
2288
+ fail("DSL_SCHEMA_TYPE_ERROR", `${context} must be a mapping object`);
2289
+ }
2290
+ return value;
2291
+ }
2292
+ function requireArray(value, context) {
2293
+ if (!Array.isArray(value)) {
2294
+ fail("DSL_SCHEMA_TYPE_ERROR", `${context} must be an array`);
2295
+ }
2296
+ return value;
2297
+ }
2298
+ function requireString(value, context) {
2299
+ if (typeof value !== "string") {
2300
+ fail("DSL_SCHEMA_TYPE_ERROR", `${context} must be a string`);
2301
+ }
2302
+ return value;
2303
+ }
2304
+ function requireNumber(value, context) {
2305
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2306
+ fail("DSL_SCHEMA_TYPE_ERROR", `${context} must be a finite number`);
2307
+ }
2308
+ return value;
2309
+ }
2310
+ function requireBoolean(value, context) {
2311
+ if (typeof value !== "boolean") {
2312
+ fail("DSL_SCHEMA_TYPE_ERROR", `${context} must be a boolean`);
2313
+ }
2314
+ return value;
2315
+ }
2316
+ function requireIdentifier(value, context) {
2317
+ const identifier = requireString(value, context);
2318
+ if (!IDENTIFIER_PATTERN.test(identifier)) {
2319
+ fail("INVALID_IDENTIFIER", `${context} must be kebab-case`);
2320
+ }
2321
+ return identifier;
2322
+ }
2323
+ function parseTuple2(value, context) {
2324
+ if (!Array.isArray(value) || value.length !== 2) {
2325
+ fail("DSL_SCHEMA_TYPE_ERROR", `${context} must be a two-number tuple`);
2326
+ }
2327
+ return [requireNumber(value[0], `${context}[0]`), requireNumber(value[1], `${context}[1]`)];
2328
+ }
2329
+ function parseTuple4(value, context) {
2330
+ if (!Array.isArray(value) || value.length !== 4) {
2331
+ fail("DSL_SCHEMA_TYPE_ERROR", `${context} must be a four-number tuple`);
2332
+ }
2333
+ return [
2334
+ requireNumber(value[0], `${context}[0]`),
2335
+ requireNumber(value[1], `${context}[1]`),
2336
+ requireNumber(value[2], `${context}[2]`),
2337
+ requireNumber(value[3], `${context}[3]`)
2338
+ ];
2339
+ }
2340
+ function parseSpriteDefinition(value, context) {
2341
+ if (Array.isArray(value)) {
2342
+ return parseTuple2(value, context);
2343
+ }
2344
+ const sprite = requireObject(value, context);
2345
+ assertKnownFields(sprite, /* @__PURE__ */ new Set(["at", "rect", "anchor"]), context);
2346
+ const parsed = {};
2347
+ if (sprite.at !== void 0) parsed.at = parseTuple2(sprite.at, `${context}.at`);
2348
+ if (sprite.rect !== void 0) parsed.rect = parseTuple4(sprite.rect, `${context}.rect`);
2349
+ if (sprite.anchor !== void 0) parsed.anchor = parseTuple2(sprite.anchor, `${context}.anchor`);
2350
+ return parsed;
2351
+ }
2352
+ function parseAssets(raw) {
2353
+ return requireArray(raw, "header.assets").map((item, index) => {
2354
+ const asset = requireObject(item, `header.assets[${index}]`);
2355
+ const assetType = asset.type === void 0 ? void 0 : requireString(asset.type, `header.assets[${index}].type`);
2356
+ if (assetType !== void 0 && assetType !== "sprite-sheet") {
2357
+ fail("ASSET_TYPE_UNSUPPORTED", `Unsupported asset type "${assetType}" in header.assets[${index}]`);
2358
+ }
2359
+ const id = requireIdentifier(asset.id, `header.assets[${index}].id`);
2360
+ if (assetType === "sprite-sheet") {
2361
+ assertKnownFields(
2362
+ asset,
2363
+ /* @__PURE__ */ new Set(["id", "type", "path", "sheetSize", "tileSize", "anchor", "sprites"]),
2364
+ `header.assets[${index}]`
2365
+ );
2366
+ const path = requireString(asset.path, `header.assets[${index}].path`);
2367
+ if (!SPRITE_SHEET_PATH_PATTERN.test(path)) {
2368
+ fail("INVALID_SPRITE_SHEET_PATH", `header.assets[${index}].path must include a supported image extension`);
2369
+ }
2370
+ const sprites = requireObject(asset.sprites, `header.assets[${index}].sprites`);
2371
+ const parsedSprites = {};
2372
+ for (const [spriteId, spriteValue] of Object.entries(sprites)) {
2373
+ if (!IDENTIFIER_PATTERN.test(spriteId)) {
2374
+ fail("INVALID_SPRITE_ID", `Sprite id "${spriteId}" must be kebab-case`);
2375
+ }
2376
+ parsedSprites[spriteId] = parseSpriteDefinition(spriteValue, `header.assets[${index}].sprites.${spriteId}`);
2377
+ }
2378
+ const parsed2 = {
2379
+ id,
2380
+ type: "sprite-sheet",
2381
+ path,
2382
+ sheetSize: parseTuple2(asset.sheetSize, `header.assets[${index}].sheetSize`),
2383
+ sprites: parsedSprites
2384
+ };
2385
+ if (asset.tileSize !== void 0)
2386
+ parsed2.tileSize = parseTuple2(asset.tileSize, `header.assets[${index}].tileSize`);
2387
+ if (asset.anchor !== void 0) parsed2.anchor = parseTuple2(asset.anchor, `header.assets[${index}].anchor`);
2388
+ return parsed2;
2389
+ }
2390
+ assertKnownFields(asset, /* @__PURE__ */ new Set(["id", "path", "anchor"]), `header.assets[${index}]`);
2391
+ const parsed = { id };
2392
+ if (asset.path !== void 0) {
2393
+ const path = requireString(asset.path, `header.assets[${index}].path`);
2394
+ if (!ASSET_PATH_PATTERN.test(path)) {
2395
+ fail("INVALID_ASSET_PATH", `header.assets[${index}].path must be a relative SVG asset path`);
2396
+ }
2397
+ parsed.path = path;
2398
+ }
2399
+ if (asset.anchor !== void 0) {
2400
+ parsed.anchor = parseTuple2(asset.anchor, `header.assets[${index}].anchor`);
2401
+ }
2402
+ return parsed;
2403
+ });
2404
+ }
2405
+ function parseGrid(raw) {
2406
+ if (raw === void 0) return void 0;
2407
+ const grid = requireObject(raw, "header.grid");
2408
+ assertKnownFields(grid, /* @__PURE__ */ new Set(["cellSize"]), "header.grid");
2409
+ return {
2410
+ cellSize: grid.cellSize === void 0 ? void 0 : requireNumber(grid.cellSize, "header.grid.cellSize")
2411
+ };
2412
+ }
2413
+ function parseFloor(raw) {
2414
+ if (raw === void 0) return void 0;
2415
+ const floor = requireObject(raw, "header.floor");
2416
+ assertKnownFields(floor, /* @__PURE__ */ new Set(["size", "origin", "layer", "visible", "asset"]), "header.floor");
2417
+ const parsed = {};
2418
+ if (floor.size !== void 0) {
2419
+ parsed.size = parseTuple2(floor.size, "header.floor.size");
2420
+ }
2421
+ if (floor.origin !== void 0) {
2422
+ parsed.origin = parseTuple2(floor.origin, "header.floor.origin");
2423
+ }
2424
+ if (floor.layer !== void 0) {
2425
+ parsed.layer = requireIdentifier(floor.layer, "header.floor.layer");
2426
+ }
2427
+ if (floor.visible !== void 0) {
2428
+ parsed.visible = requireBoolean(floor.visible, "header.floor.visible");
2429
+ }
2430
+ if (floor.asset !== void 0) {
2431
+ parsed.asset = requireIdentifier(floor.asset, "header.floor.asset");
2432
+ }
2433
+ return parsed;
2434
+ }
2435
+ function parseLayers(raw) {
2436
+ return requireArray(raw, "header.layers").map((item, index) => {
2437
+ const layer = requireObject(item, `header.layers[${index}]`);
2438
+ assertKnownFields(layer, /* @__PURE__ */ new Set(["name", "order"]), `header.layers[${index}]`);
2439
+ const parsed = {
2440
+ name: requireIdentifier(layer.name, `header.layers[${index}].name`)
2441
+ };
2442
+ if (layer.order !== void 0) {
2443
+ parsed.order = requireNumber(layer.order, `header.layers[${index}].order`);
2444
+ }
2445
+ return parsed;
2446
+ });
2447
+ }
2448
+ function parseHeader(raw) {
2449
+ const header = requireObject(raw, "header");
2450
+ assertKnownFields(
2451
+ header,
2452
+ /* @__PURE__ */ new Set(["version", "name", "className", "assetBaseUrl", "assets", "grid", "floor", "theme", "layers"]),
2453
+ "header"
2454
+ );
2455
+ const parsed = {
2456
+ assets: parseAssets(header.assets),
2457
+ layers: parseLayers(header.layers)
2458
+ };
2459
+ const floor = parseFloor(header.floor);
2460
+ if (floor !== void 0) {
2461
+ parsed.floor = floor;
2462
+ }
2463
+ if (header.version !== void 0) {
2464
+ parsed.version = requireString(header.version, "header.version");
2465
+ }
2466
+ if (header.name !== void 0) {
2467
+ parsed.name = requireString(header.name, "header.name");
2468
+ }
2469
+ if (header.className !== void 0) {
2470
+ parsed.className = requireString(header.className, "header.className");
2471
+ }
2472
+ if (header.assetBaseUrl !== void 0) {
2473
+ parsed.assetBaseUrl = requireString(header.assetBaseUrl, "header.assetBaseUrl");
2474
+ }
2475
+ if (header.grid !== void 0) {
2476
+ parsed.grid = parseGrid(header.grid);
2477
+ }
2478
+ if (header.theme !== void 0) {
2479
+ parsed.theme = requireString(header.theme, "header.theme");
2480
+ }
2481
+ return parsed;
2482
+ }
2483
+ function parseAmbient(raw, context) {
2484
+ return requireArray(raw, context).map((item, index) => {
2485
+ const ambient = requireObject(item, `${context}[${index}]`);
2486
+ assertKnownFields(ambient, /* @__PURE__ */ new Set(["name", "infinite", "iterations"]), `${context}[${index}]`);
2487
+ const parsed = {
2488
+ name: requireIdentifier(ambient.name, `${context}[${index}].name`)
2489
+ };
2490
+ if (ambient.infinite !== void 0) {
2491
+ parsed.infinite = requireBoolean(ambient.infinite, `${context}[${index}].infinite`);
2492
+ }
2493
+ if (ambient.iterations !== void 0) {
2494
+ parsed.iterations = requireNumber(ambient.iterations, `${context}[${index}].iterations`);
2495
+ }
2496
+ return parsed;
2497
+ });
2498
+ }
2499
+ function parsePointArray(raw, context) {
2500
+ return requireArray(raw, context).map((point, index) => parseTuple2(point, `${context}[${index}]`));
2501
+ }
2502
+ function parseTextContent(raw, context, requireValue = true) {
2503
+ const text = requireObject(raw, context);
2504
+ assertKnownFields(
2505
+ text,
2506
+ /* @__PURE__ */ new Set(["value", "align", "placement", "fontSize", "fontWeight", "lineHeight", "fill"]),
2507
+ context
2508
+ );
2509
+ const parsed = {};
2510
+ if (requireValue || text.value !== void 0) {
2511
+ parsed.value = requireString(text.value, `${context}.value`);
2512
+ }
2513
+ if (text.align !== void 0) {
2514
+ parsed.align = requireString(text.align, `${context}.align`);
2515
+ }
2516
+ if (text.placement !== void 0) {
2517
+ parsed.placement = requireString(text.placement, `${context}.placement`);
2518
+ }
2519
+ if (text.fontSize !== void 0) {
2520
+ parsed.fontSize = requireNumber(text.fontSize, `${context}.fontSize`);
2521
+ }
2522
+ if (text.fontWeight !== void 0) {
2523
+ if (typeof text.fontWeight === "number") {
2524
+ parsed.fontWeight = requireNumber(text.fontWeight, `${context}.fontWeight`);
2525
+ } else {
2526
+ parsed.fontWeight = requireString(text.fontWeight, `${context}.fontWeight`);
2527
+ }
2528
+ }
2529
+ if (text.lineHeight !== void 0) {
2530
+ parsed.lineHeight = requireNumber(text.lineHeight, `${context}.lineHeight`);
2531
+ }
2532
+ if (text.fill !== void 0) {
2533
+ parsed.fill = requireString(text.fill, `${context}.fill`);
2534
+ }
2535
+ return parsed;
2536
+ }
2537
+ function parsePrimitiveStyle(raw, context, allowed) {
2538
+ assertKnownFields(raw, new Set(allowed), context);
2539
+ const parsed = {};
2540
+ if (raw.fill !== void 0) parsed.fill = requireString(raw.fill, `${context}.fill`);
2541
+ if (raw.stroke !== void 0) parsed.stroke = requireString(raw.stroke, `${context}.stroke`);
2542
+ if (raw.strokeWidth !== void 0) {
2543
+ parsed.strokeWidth = requireNumber(raw.strokeWidth, `${context}.strokeWidth`);
2544
+ }
2545
+ if (raw.opacity !== void 0) {
2546
+ parsed.opacity = requireNumber(raw.opacity, `${context}.opacity`);
2547
+ }
2548
+ if (raw.dash !== void 0) parsed.dash = parseTuple2(raw.dash, `${context}.dash`);
2549
+ return parsed;
2550
+ }
2551
+ function parsePrimitiveContent(raw, context, requireGeometry = true) {
2552
+ const primitive = requireObject(raw, context);
2553
+ assertKnownFields(primitive, /* @__PURE__ */ new Set(["rectangle", "circle", "polygon", "line"]), context);
2554
+ const parsed = {};
2555
+ if (primitive.rectangle !== void 0) {
2556
+ const rectangle = requireObject(primitive.rectangle, `${context}.rectangle`);
2557
+ parsed.rectangle = parsePrimitiveStyle(rectangle, `${context}.rectangle`, [
2558
+ "fill",
2559
+ "stroke",
2560
+ "strokeWidth",
2561
+ "opacity",
2562
+ "dash",
2563
+ "rx"
2564
+ ]);
2565
+ if (rectangle.rx !== void 0) {
2566
+ parsed.rectangle.rx = requireNumber(rectangle.rx, `${context}.rectangle.rx`);
2567
+ }
2568
+ }
2569
+ if (primitive.circle !== void 0) {
2570
+ parsed.circle = parsePrimitiveStyle(requireObject(primitive.circle, `${context}.circle`), `${context}.circle`, [
2571
+ "fill",
2572
+ "stroke",
2573
+ "strokeWidth",
2574
+ "opacity",
2575
+ "dash"
2576
+ ]);
2577
+ }
2578
+ if (primitive.polygon !== void 0) {
2579
+ const polygon = requireObject(primitive.polygon, `${context}.polygon`);
2580
+ parsed.polygon = {
2581
+ ...parsePrimitiveStyle(polygon, `${context}.polygon`, [
2582
+ "points",
2583
+ "fill",
2584
+ "stroke",
2585
+ "strokeWidth",
2586
+ "opacity",
2587
+ "dash"
2588
+ ])
2589
+ };
2590
+ if (requireGeometry || polygon.points !== void 0) {
2591
+ parsed.polygon.points = parsePointArray(polygon.points, `${context}.polygon.points`);
2592
+ }
2593
+ }
2594
+ if (primitive.line !== void 0) {
2595
+ const line = requireObject(primitive.line, `${context}.line`);
2596
+ const parsedLine = {
2597
+ ...parsePrimitiveStyle(line, `${context}.line`, [
2598
+ "points",
2599
+ "stroke",
2600
+ "strokeWidth",
2601
+ "opacity",
2602
+ "dash",
2603
+ "lineCap",
2604
+ "lineJoin"
2605
+ ])
2606
+ };
2607
+ if (requireGeometry || line.points !== void 0) {
2608
+ parsedLine.points = parsePointArray(line.points, `${context}.line.points`);
2609
+ }
2610
+ if (line.lineCap !== void 0) {
2611
+ parsedLine.lineCap = requireString(line.lineCap, `${context}.line.lineCap`);
2612
+ }
2613
+ if (line.lineJoin !== void 0) {
2614
+ parsedLine.lineJoin = requireString(line.lineJoin, `${context}.line.lineJoin`);
2615
+ }
2616
+ parsed.line = parsedLine;
2617
+ }
2618
+ return parsed;
2619
+ }
2620
+ function parseActivity(raw, context) {
2621
+ const value = requireObject(raw, context);
2622
+ assertKnownFields(value, /* @__PURE__ */ new Set(["state", "color"]), context);
2623
+ const parsed = { state: requireString(value.state, `${context}.state`) };
2624
+ if (value.color !== void 0) parsed.color = requireString(value.color, `${context}.color`);
2625
+ return parsed;
2626
+ }
2627
+ function parseMessage(raw, context) {
2628
+ const value = requireObject(raw, context);
2629
+ assertKnownFields(value, /* @__PURE__ */ new Set(["kind", "color", "size", "duration", "count", "enabled"]), context);
2630
+ const parsed = {};
2631
+ if (value.kind !== void 0) parsed.kind = requireString(value.kind, `${context}.kind`);
2632
+ if (value.color !== void 0) parsed.color = requireString(value.color, `${context}.color`);
2633
+ if (value.size !== void 0) parsed.size = requireNumber(value.size, `${context}.size`);
2634
+ if (value.duration !== void 0) parsed.duration = requireNumber(value.duration, `${context}.duration`);
2635
+ if (value.count !== void 0) parsed.count = requireNumber(value.count, `${context}.count`);
2636
+ if (value.enabled !== void 0) parsed.enabled = requireBoolean(value.enabled, `${context}.enabled`);
2637
+ return parsed;
2638
+ }
2639
+ function parsePlacement(raw, context) {
2640
+ const element = requireObject(raw, context);
2641
+ assertKnownFields(
2642
+ element,
2643
+ /* @__PURE__ */ new Set(["id", "asset", "at", "size", "layer", "enter", "exit", "ambient", "text", "primitive", "activity"]),
2644
+ context
2645
+ );
2646
+ const parsed = {
2647
+ id: requireIdentifier(element.id, `${context}.id`),
2648
+ asset: requireIdentifier(element.asset, `${context}.asset`),
2649
+ at: parseTuple2(element.at, `${context}.at`)
2650
+ };
2651
+ if (element.size !== void 0) {
2652
+ parsed.size = requireNumber(element.size, `${context}.size`);
2653
+ }
2654
+ if (element.layer !== void 0) {
2655
+ parsed.layer = requireIdentifier(element.layer, `${context}.layer`);
2656
+ }
2657
+ if (element.enter !== void 0) {
2658
+ parsed.enter = requireString(element.enter, `${context}.enter`);
2659
+ }
2660
+ if (element.exit !== void 0) {
2661
+ parsed.exit = requireString(element.exit, `${context}.exit`);
2662
+ }
2663
+ if (element.ambient !== void 0) {
2664
+ parsed.ambient = parseAmbient(element.ambient, `${context}.ambient`);
2665
+ }
2666
+ if (element.text !== void 0) {
2667
+ parsed.text = parseTextContent(element.text, `${context}.text`);
2668
+ }
2669
+ if (element.activity !== void 0) parsed.activity = parseActivity(element.activity, `${context}.activity`);
2670
+ if (element.primitive !== void 0) {
2671
+ parsed.primitive = parsePrimitiveContent(element.primitive, `${context}.primitive`);
2672
+ }
2673
+ return parsed;
2674
+ }
2675
+ function parsePatch(raw, context) {
2676
+ const patch = requireObject(raw, context);
2677
+ assertKnownFields(
2678
+ patch,
2679
+ /* @__PURE__ */ new Set(["id", "asset", "at", "size", "layer", "enter", "exit", "ambient", "text", "primitive", "activity"]),
2680
+ context
2681
+ );
2682
+ const parsed = {
2683
+ id: requireIdentifier(patch.id, `${context}.id`)
2684
+ };
2685
+ if (patch.asset !== void 0) parsed.asset = requireIdentifier(patch.asset, `${context}.asset`);
2686
+ if (patch.activity !== void 0) parsed.activity = parseActivity(patch.activity, `${context}.activity`);
2687
+ if (patch.at !== void 0) {
2688
+ parsed.at = parseTuple2(patch.at, `${context}.at`);
2689
+ }
2690
+ if (patch.size !== void 0) {
2691
+ parsed.size = requireNumber(patch.size, `${context}.size`);
2692
+ }
2693
+ if (patch.layer !== void 0) {
2694
+ parsed.layer = requireIdentifier(patch.layer, `${context}.layer`);
2695
+ }
2696
+ if (patch.enter !== void 0) {
2697
+ parsed.enter = requireString(patch.enter, `${context}.enter`);
2698
+ }
2699
+ if (patch.exit !== void 0) {
2700
+ parsed.exit = requireString(patch.exit, `${context}.exit`);
2701
+ }
2702
+ if (patch.ambient !== void 0) {
2703
+ parsed.ambient = parseAmbient(patch.ambient, `${context}.ambient`);
2704
+ }
2705
+ if (patch.text !== void 0) {
2706
+ parsed.text = parseTextContent(patch.text, `${context}.text`, false);
2707
+ }
2708
+ if (patch.primitive !== void 0) {
2709
+ parsed.primitive = parsePrimitiveContent(patch.primitive, `${context}.primitive`, false);
2710
+ }
2711
+ return parsed;
2712
+ }
2713
+ function parseRemoval(raw, context) {
2714
+ const removal = requireObject(raw, context);
2715
+ assertKnownFields(removal, /* @__PURE__ */ new Set(["id", "exit"]), context);
2716
+ const parsed = {
2717
+ id: requireIdentifier(removal.id, `${context}.id`)
2718
+ };
2719
+ if (removal.exit !== void 0) {
2720
+ parsed.exit = requireString(removal.exit, `${context}.exit`);
2721
+ }
2722
+ return parsed;
2723
+ }
2724
+ function parseRoute(raw, context) {
2725
+ return requireArray(raw, context).map((point, index) => parseTuple2(point, `${context}[${index}]`));
2726
+ }
2727
+ function parseEndpointRef(raw, context) {
2728
+ const endpoint = requireObject(raw, context);
2729
+ assertKnownFields(endpoint, /* @__PURE__ */ new Set(["element", "at", "side", "offset"]), context);
2730
+ const parsed = {};
2731
+ if (endpoint.element !== void 0) {
2732
+ parsed.element = requireIdentifier(endpoint.element, `${context}.element`);
2733
+ }
2734
+ if (endpoint.at !== void 0) {
2735
+ parsed.at = parseTuple2(endpoint.at, `${context}.at`);
2736
+ }
2737
+ if (endpoint.side !== void 0) {
2738
+ parsed.side = requireString(endpoint.side, `${context}.side`);
2739
+ }
2740
+ if (endpoint.offset !== void 0) {
2741
+ parsed.offset = requireNumber(endpoint.offset, `${context}.offset`);
2742
+ }
2743
+ return parsed;
2744
+ }
2745
+ function parseRouting(raw, context) {
2746
+ const routing = requireObject(raw, context);
2747
+ assertKnownFields(routing, /* @__PURE__ */ new Set(["mode", "avoid", "clearance", "gridStep", "maxBends", "prefer"]), context);
2748
+ const parsed = {};
2749
+ if (routing.mode !== void 0) {
2750
+ parsed.mode = requireString(routing.mode, `${context}.mode`);
2751
+ }
2752
+ if (routing.avoid !== void 0) {
2753
+ if (Array.isArray(routing.avoid)) {
2754
+ parsed.avoid = requireArray(routing.avoid, `${context}.avoid`).map(
2755
+ (item, index) => requireIdentifier(item, `${context}.avoid[${index}]`)
2756
+ );
2757
+ } else {
2758
+ parsed.avoid = requireString(routing.avoid, `${context}.avoid`);
2759
+ }
2760
+ }
2761
+ if (routing.clearance !== void 0) {
2762
+ parsed.clearance = requireNumber(routing.clearance, `${context}.clearance`);
2763
+ }
2764
+ if (routing.gridStep !== void 0) {
2765
+ parsed.gridStep = requireNumber(routing.gridStep, `${context}.gridStep`);
2766
+ }
2767
+ if (routing.maxBends !== void 0) {
2768
+ parsed.maxBends = requireNumber(routing.maxBends, `${context}.maxBends`);
2769
+ }
2770
+ if (routing.prefer !== void 0) {
2771
+ parsed.prefer = requireString(routing.prefer, `${context}.prefer`);
2772
+ }
2773
+ return parsed;
2774
+ }
2775
+ function parseConnectorStyle(raw, context) {
2776
+ const style = requireObject(raw, context);
2777
+ assertKnownFields(
2778
+ style,
2779
+ /* @__PURE__ */ new Set([
2780
+ "variant",
2781
+ "pattern",
2782
+ "stroke",
2783
+ "strokeWidth",
2784
+ "opacity",
2785
+ "dash",
2786
+ "outline",
2787
+ "outlineWidth",
2788
+ "lane",
2789
+ "cornerRadius",
2790
+ "glow",
2791
+ "glowWidth"
2792
+ ]),
2793
+ context
2794
+ );
2795
+ const parsed = {};
2796
+ if (style.cornerRadius !== void 0)
2797
+ parsed.cornerRadius = requireNumber(style.cornerRadius, `${context}.cornerRadius`);
2798
+ if (style.glowWidth !== void 0) parsed.glowWidth = requireNumber(style.glowWidth, `${context}.glowWidth`);
2799
+ if (style.glow !== void 0) parsed.glow = requireString(style.glow, `${context}.glow`);
2800
+ if (style.variant !== void 0) {
2801
+ parsed.variant = requireString(style.variant, `${context}.variant`);
2802
+ }
2803
+ if (style.pattern !== void 0) {
2804
+ parsed.pattern = requireString(style.pattern, `${context}.pattern`);
2805
+ }
2806
+ if (style.stroke !== void 0) {
2807
+ parsed.stroke = requireString(style.stroke, `${context}.stroke`);
2808
+ }
2809
+ if (style.strokeWidth !== void 0) {
2810
+ parsed.strokeWidth = requireNumber(style.strokeWidth, `${context}.strokeWidth`);
2811
+ }
2812
+ if (style.opacity !== void 0) {
2813
+ parsed.opacity = requireNumber(style.opacity, `${context}.opacity`);
2814
+ }
2815
+ if (style.dash !== void 0) {
2816
+ parsed.dash = parseTuple2(style.dash, `${context}.dash`);
2817
+ }
2818
+ if (style.outline !== void 0) {
2819
+ parsed.outline = requireString(style.outline, `${context}.outline`);
2820
+ }
2821
+ if (style.outlineWidth !== void 0) {
2822
+ parsed.outlineWidth = requireNumber(style.outlineWidth, `${context}.outlineWidth`);
2823
+ }
2824
+ if (style.lane !== void 0) {
2825
+ parsed.lane = requireString(style.lane, `${context}.lane`);
2826
+ }
2827
+ return parsed;
2828
+ }
2829
+ function parseConnectionCommon(raw, context) {
2830
+ const connection = requireObject(raw, context);
2831
+ assertKnownFields(
2832
+ connection,
2833
+ /* @__PURE__ */ new Set([
2834
+ "id",
2835
+ "route",
2836
+ "from",
2837
+ "to",
2838
+ "routing",
2839
+ "layer",
2840
+ "style",
2841
+ "start",
2842
+ "end",
2843
+ "direction",
2844
+ "message",
2845
+ "enter",
2846
+ "exit",
2847
+ "ambient"
2848
+ ]),
2849
+ context
2850
+ );
2851
+ const parsed = {
2852
+ id: requireIdentifier(connection.id, `${context}.id`)
2853
+ };
2854
+ if (connection.message !== void 0) parsed.message = parseMessage(connection.message, `${context}.message`);
2855
+ if (connection.route !== void 0) {
2856
+ parsed.route = parseRoute(connection.route, `${context}.route`);
2857
+ }
2858
+ if (connection.from !== void 0) {
2859
+ parsed.from = parseEndpointRef(connection.from, `${context}.from`);
2860
+ }
2861
+ if (connection.to !== void 0) {
2862
+ parsed.to = parseEndpointRef(connection.to, `${context}.to`);
2863
+ }
2864
+ if (connection.routing !== void 0) {
2865
+ parsed.routing = parseRouting(connection.routing, `${context}.routing`);
2866
+ }
2867
+ if (connection.layer !== void 0) {
2868
+ parsed.layer = requireIdentifier(connection.layer, `${context}.layer`);
2869
+ }
2870
+ if (connection.style !== void 0) {
2871
+ parsed.style = parseConnectorStyle(connection.style, `${context}.style`);
2872
+ }
2873
+ if (connection.start !== void 0) {
2874
+ parsed.start = requireString(connection.start, `${context}.start`);
2875
+ }
2876
+ if (connection.end !== void 0) {
2877
+ parsed.end = requireString(connection.end, `${context}.end`);
2878
+ }
2879
+ if (connection.direction !== void 0) {
2880
+ parsed.direction = requireString(connection.direction, `${context}.direction`);
2881
+ }
2882
+ if (connection.enter !== void 0) {
2883
+ parsed.enter = requireString(connection.enter, `${context}.enter`);
2884
+ }
2885
+ if (connection.exit !== void 0) {
2886
+ parsed.exit = requireString(connection.exit, `${context}.exit`);
2887
+ }
2888
+ if (connection.ambient !== void 0) {
2889
+ parsed.ambient = parseAmbient(connection.ambient, `${context}.ambient`);
2890
+ }
2891
+ return parsed;
2892
+ }
2893
+ function parseConnectionPlacement(raw, context) {
2894
+ return parseConnectionCommon(raw, context);
2895
+ }
2896
+ function parseConnectionPatch(raw, context) {
2897
+ return parseConnectionCommon(raw, context);
2898
+ }
2899
+ function parseConnectionRemoval(raw, context) {
2900
+ const removal = requireObject(raw, context);
2901
+ assertKnownFields(removal, /* @__PURE__ */ new Set(["id", "exit"]), context);
2902
+ const parsed = {
2903
+ id: requireIdentifier(removal.id, `${context}.id`)
2904
+ };
2905
+ if (removal.exit !== void 0) {
2906
+ parsed.exit = requireString(removal.exit, `${context}.exit`);
2907
+ }
2908
+ return parsed;
2909
+ }
2910
+ function parseCamera(raw, context) {
2911
+ const camera = requireObject(raw, context);
2912
+ assertKnownFields(camera, /* @__PURE__ */ new Set(["target", "padding", "duration", "easing"]), context);
2913
+ const target = requireObject(camera.target, `${context}.target`);
2914
+ assertKnownFields(target, /* @__PURE__ */ new Set(["element", "area", "reset"]), `${context}.target`);
2915
+ const parsedTarget = {};
2916
+ if (target.element !== void 0) {
2917
+ parsedTarget.element = requireIdentifier(target.element, `${context}.target.element`);
2918
+ }
2919
+ if (target.area !== void 0) {
2920
+ const area = requireObject(target.area, `${context}.target.area`);
2921
+ assertKnownFields(area, /* @__PURE__ */ new Set(["at", "size"]), `${context}.target.area`);
2922
+ parsedTarget.area = {
2923
+ at: parseTuple2(area.at, `${context}.target.area.at`),
2924
+ size: parseTuple2(area.size, `${context}.target.area.size`)
2925
+ };
2926
+ }
2927
+ if (target.reset !== void 0) {
2928
+ parsedTarget.reset = requireBoolean(target.reset, `${context}.target.reset`);
2929
+ }
2930
+ const parsed = { target: parsedTarget };
2931
+ if (camera.padding !== void 0) {
2932
+ parsed.padding = requireNumber(camera.padding, `${context}.padding`);
2933
+ }
2934
+ if (camera.duration !== void 0) {
2935
+ parsed.duration = requireNumber(camera.duration, `${context}.duration`);
2936
+ }
2937
+ if (camera.easing !== void 0) {
2938
+ parsed.easing = requireString(camera.easing, `${context}.easing`);
2939
+ }
2940
+ return parsed;
2941
+ }
2942
+ function parseScenes(raw) {
2943
+ return requireArray(raw, "scenes").map((item, index) => {
2944
+ const scene = requireObject(item, `scenes[${index}]`);
2945
+ assertKnownFields(
2946
+ scene,
2947
+ /* @__PURE__ */ new Set(["id", "elements", "connections", "add", "update", "remove", "camera"]),
2948
+ `scenes[${index}]`
2949
+ );
2950
+ const parsed = {
2951
+ id: requireIdentifier(scene.id, `scenes[${index}].id`)
2952
+ };
2953
+ if (scene.elements !== void 0) {
2954
+ parsed.elements = requireArray(scene.elements, `scenes[${index}].elements`).map(
2955
+ (element, elementIndex) => parsePlacement(element, `scenes[${index}].elements[${elementIndex}]`)
2956
+ );
2957
+ }
2958
+ if (scene.connections !== void 0) {
2959
+ parsed.connections = requireArray(scene.connections, `scenes[${index}].connections`).map(
2960
+ (connection, connectionIndex) => parseConnectionPlacement(connection, `scenes[${index}].connections[${connectionIndex}]`)
2961
+ );
2962
+ }
2963
+ if (scene.add !== void 0) {
2964
+ const add = requireObject(scene.add, `scenes[${index}].add`);
2965
+ assertKnownFields(add, /* @__PURE__ */ new Set(["elements", "connections"]), `scenes[${index}].add`);
2966
+ parsed.add = {};
2967
+ if (add.elements !== void 0) {
2968
+ parsed.add.elements = requireArray(add.elements, `scenes[${index}].add.elements`).map(
2969
+ (element, elementIndex) => parsePlacement(element, `scenes[${index}].add.elements[${elementIndex}]`)
2970
+ );
2971
+ }
2972
+ if (add.connections !== void 0) {
2973
+ parsed.add.connections = requireArray(add.connections, `scenes[${index}].add.connections`).map(
2974
+ (connection, connectionIndex) => parseConnectionPlacement(connection, `scenes[${index}].add.connections[${connectionIndex}]`)
2975
+ );
2976
+ }
2977
+ }
2978
+ if (scene.update !== void 0) {
2979
+ const update = requireObject(scene.update, `scenes[${index}].update`);
2980
+ assertKnownFields(update, /* @__PURE__ */ new Set(["elements", "connections"]), `scenes[${index}].update`);
2981
+ parsed.update = {};
2982
+ if (update.elements !== void 0) {
2983
+ parsed.update.elements = requireArray(update.elements, `scenes[${index}].update.elements`).map(
2984
+ (patch, patchIndex) => parsePatch(patch, `scenes[${index}].update.elements[${patchIndex}]`)
2985
+ );
2986
+ }
2987
+ if (update.connections !== void 0) {
2988
+ parsed.update.connections = requireArray(update.connections, `scenes[${index}].update.connections`).map(
2989
+ (patch, patchIndex) => parseConnectionPatch(patch, `scenes[${index}].update.connections[${patchIndex}]`)
2990
+ );
2991
+ }
2992
+ }
2993
+ if (scene.remove !== void 0) {
2994
+ const remove = requireObject(scene.remove, `scenes[${index}].remove`);
2995
+ assertKnownFields(remove, /* @__PURE__ */ new Set(["elements", "connections"]), `scenes[${index}].remove`);
2996
+ parsed.remove = {};
2997
+ if (remove.elements !== void 0) {
2998
+ parsed.remove.elements = requireArray(remove.elements, `scenes[${index}].remove.elements`).map(
2999
+ (removal, removalIndex) => parseRemoval(removal, `scenes[${index}].remove.elements[${removalIndex}]`)
3000
+ );
3001
+ }
3002
+ if (remove.connections !== void 0) {
3003
+ parsed.remove.connections = requireArray(remove.connections, `scenes[${index}].remove.connections`).map(
3004
+ (removal, removalIndex) => parseConnectionRemoval(removal, `scenes[${index}].remove.connections[${removalIndex}]`)
3005
+ );
3006
+ }
3007
+ }
3008
+ if (scene.camera !== void 0) {
3009
+ parsed.camera = parseCamera(scene.camera, `scenes[${index}].camera`);
3010
+ }
3011
+ return parsed;
3012
+ });
3013
+ }
3014
+ function parseScene(dsl) {
3015
+ let raw;
3016
+ try {
3017
+ raw = parse(dsl);
3018
+ } catch (err) {
3019
+ const message = err instanceof Error ? err.message : String(err);
3020
+ throw new ParseError("DSL_PARSE_SYNTAX_ERROR", `YAML syntax error: ${message}`, { line: 0, column: 0 });
3021
+ }
3022
+ if (!isObject(raw)) {
3023
+ fail("DSL_PARSE_SYNTAX_ERROR", "DSL must be a YAML mapping at the top level");
3024
+ }
3025
+ assertKnownFields(raw, /* @__PURE__ */ new Set(["header", "scenes"]), "top level");
3026
+ return {
3027
+ header: parseHeader(raw.header),
3028
+ scenes: parseScenes(raw.scenes)
3029
+ };
3030
+ }
3031
+
3032
+ export { fromJson as a, toJson as b, compileScene as c, deriveProgresses as d, fromJs as f, parseScene as p, resolveSceneSnapshots as r, toJs as t, validateScene as v };
3033
+ //# sourceMappingURL=scene-parser-BNXXIAqj.js.map