worldorbit 2.5.2

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 (113) hide show
  1. package/LICENSE.md +5 -0
  2. package/README.md +250 -0
  3. package/dist/browser/core/dist/index.js +4009 -0
  4. package/dist/browser/markdown/dist/index.js +3951 -0
  5. package/dist/browser/viewer/dist/index.js +5981 -0
  6. package/dist/constants.d.ts +8 -0
  7. package/dist/constants.js +84 -0
  8. package/dist/errors.d.ts +7 -0
  9. package/dist/errors.js +16 -0
  10. package/dist/index.d.ts +18 -0
  11. package/dist/index.js +25 -0
  12. package/dist/normalize.d.ts +2 -0
  13. package/dist/normalize.js +243 -0
  14. package/dist/parse.d.ts +2 -0
  15. package/dist/parse.js +126 -0
  16. package/dist/render.d.ts +6 -0
  17. package/dist/render.js +683 -0
  18. package/dist/tokenize.d.ts +4 -0
  19. package/dist/tokenize.js +68 -0
  20. package/dist/types.d.ts +208 -0
  21. package/dist/types.js +1 -0
  22. package/dist/unpkg/core/dist/index.js +4081 -0
  23. package/dist/unpkg/markdown/dist/index.js +3979 -0
  24. package/dist/unpkg/test.html +1 -0
  25. package/dist/unpkg/viewer/dist/index.js +6038 -0
  26. package/dist/unpkg/worldorbit-core.min.js +5 -0
  27. package/dist/unpkg/worldorbit-markdown.min.js +81 -0
  28. package/dist/unpkg/worldorbit-viewer.min.js +232 -0
  29. package/dist/unpkg/worldorbit.d.ts +2 -0
  30. package/dist/unpkg/worldorbit.js +2 -0
  31. package/dist/unpkg/worldorbit.min.js +236 -0
  32. package/dist/validate.d.ts +2 -0
  33. package/dist/validate.js +31 -0
  34. package/dist/viewer-state.d.ts +16 -0
  35. package/dist/viewer-state.js +130 -0
  36. package/dist/viewer.d.ts +2 -0
  37. package/dist/viewer.js +434 -0
  38. package/package.json +64 -0
  39. package/packages/core/README.md +13 -0
  40. package/packages/core/dist/atlas-edit.d.ts +11 -0
  41. package/packages/core/dist/atlas-edit.js +210 -0
  42. package/packages/core/dist/diagnostics.d.ts +10 -0
  43. package/packages/core/dist/diagnostics.js +109 -0
  44. package/packages/core/dist/draft-parse.d.ts +3 -0
  45. package/packages/core/dist/draft-parse.js +642 -0
  46. package/packages/core/dist/draft.d.ts +15 -0
  47. package/packages/core/dist/draft.js +343 -0
  48. package/packages/core/dist/errors.d.ts +7 -0
  49. package/packages/core/dist/errors.js +16 -0
  50. package/packages/core/dist/format.d.ts +4 -0
  51. package/packages/core/dist/format.js +364 -0
  52. package/packages/core/dist/index.d.ts +28 -0
  53. package/packages/core/dist/index.js +44 -0
  54. package/packages/core/dist/load.d.ts +4 -0
  55. package/packages/core/dist/load.js +130 -0
  56. package/packages/core/dist/markdown.d.ts +2 -0
  57. package/packages/core/dist/markdown.js +37 -0
  58. package/packages/core/dist/normalize.d.ts +2 -0
  59. package/packages/core/dist/normalize.js +304 -0
  60. package/packages/core/dist/parse.d.ts +2 -0
  61. package/packages/core/dist/parse.js +133 -0
  62. package/packages/core/dist/scene.d.ts +3 -0
  63. package/packages/core/dist/scene.js +1484 -0
  64. package/packages/core/dist/schema.d.ts +8 -0
  65. package/packages/core/dist/schema.js +298 -0
  66. package/packages/core/dist/tokenize.d.ts +4 -0
  67. package/packages/core/dist/tokenize.js +68 -0
  68. package/packages/core/dist/types.d.ts +382 -0
  69. package/packages/core/dist/types.js +1 -0
  70. package/packages/core/dist/validate.d.ts +2 -0
  71. package/packages/core/dist/validate.js +56 -0
  72. package/packages/editor/dist/editor.d.ts +2 -0
  73. package/packages/editor/dist/editor.js +2620 -0
  74. package/packages/editor/dist/index.d.ts +2 -0
  75. package/packages/editor/dist/index.js +1 -0
  76. package/packages/editor/dist/types.d.ts +53 -0
  77. package/packages/editor/dist/types.js +1 -0
  78. package/packages/markdown/README.md +9 -0
  79. package/packages/markdown/dist/html.d.ts +3 -0
  80. package/packages/markdown/dist/html.js +57 -0
  81. package/packages/markdown/dist/index.d.ts +4 -0
  82. package/packages/markdown/dist/index.js +3 -0
  83. package/packages/markdown/dist/rehype.d.ts +10 -0
  84. package/packages/markdown/dist/rehype.js +49 -0
  85. package/packages/markdown/dist/remark.d.ts +9 -0
  86. package/packages/markdown/dist/remark.js +28 -0
  87. package/packages/markdown/dist/types.d.ts +11 -0
  88. package/packages/markdown/dist/types.js +1 -0
  89. package/packages/viewer/README.md +12 -0
  90. package/packages/viewer/dist/atlas-state.d.ts +12 -0
  91. package/packages/viewer/dist/atlas-state.js +251 -0
  92. package/packages/viewer/dist/atlas-viewer.d.ts +2 -0
  93. package/packages/viewer/dist/atlas-viewer.js +448 -0
  94. package/packages/viewer/dist/custom-element.d.ts +1 -0
  95. package/packages/viewer/dist/custom-element.js +64 -0
  96. package/packages/viewer/dist/embed.d.ts +20 -0
  97. package/packages/viewer/dist/embed.js +138 -0
  98. package/packages/viewer/dist/index.d.ts +9 -0
  99. package/packages/viewer/dist/index.js +8 -0
  100. package/packages/viewer/dist/minimap.d.ts +3 -0
  101. package/packages/viewer/dist/minimap.js +63 -0
  102. package/packages/viewer/dist/render.d.ts +6 -0
  103. package/packages/viewer/dist/render.js +585 -0
  104. package/packages/viewer/dist/theme.d.ts +4 -0
  105. package/packages/viewer/dist/theme.js +98 -0
  106. package/packages/viewer/dist/tooltip.d.ts +3 -0
  107. package/packages/viewer/dist/tooltip.js +154 -0
  108. package/packages/viewer/dist/types.d.ts +256 -0
  109. package/packages/viewer/dist/types.js +1 -0
  110. package/packages/viewer/dist/viewer-state.d.ts +19 -0
  111. package/packages/viewer/dist/viewer-state.js +162 -0
  112. package/packages/viewer/dist/viewer.d.ts +2 -0
  113. package/packages/viewer/dist/viewer.js +1156 -0
@@ -0,0 +1,4009 @@
1
+ "use strict";
2
+ (() => {
3
+ // packages/core/dist/errors.js
4
+ var WorldOrbitError = class _WorldOrbitError extends Error {
5
+ line;
6
+ column;
7
+ constructor(message, line, column) {
8
+ const locationSuffix = line === void 0 ? "" : ` (line ${line}${column === void 0 ? "" : `, column ${column}`})`;
9
+ super(`${message}${locationSuffix}`);
10
+ this.name = "WorldOrbitError";
11
+ this.line = line;
12
+ this.column = column;
13
+ }
14
+ static fromLocation(message, location) {
15
+ return new _WorldOrbitError(message, location?.line, location?.column);
16
+ }
17
+ };
18
+
19
+ // packages/core/dist/schema.js
20
+ var ALL_OBJECTS = [
21
+ "system",
22
+ "star",
23
+ "planet",
24
+ "moon",
25
+ "belt",
26
+ "asteroid",
27
+ "comet",
28
+ "ring",
29
+ "structure",
30
+ "phenomenon"
31
+ ];
32
+ var NON_SYSTEM_OBJECTS = ALL_OBJECTS.filter((objectType) => objectType !== "system");
33
+ var IMAGE_OBJECTS = [
34
+ "star",
35
+ "planet",
36
+ "moon",
37
+ "asteroid",
38
+ "comet",
39
+ "structure",
40
+ "phenomenon"
41
+ ];
42
+ var ANCHORED_OBJECTS = ["structure", "phenomenon"];
43
+ var ORBITAL_OBJECTS = [
44
+ "star",
45
+ "planet",
46
+ "moon",
47
+ "belt",
48
+ "asteroid",
49
+ "comet",
50
+ "ring",
51
+ "structure",
52
+ "phenomenon"
53
+ ];
54
+ var FREE_OBJECTS = [
55
+ "star",
56
+ "planet",
57
+ "moon",
58
+ "belt",
59
+ "asteroid",
60
+ "comet",
61
+ "ring",
62
+ "structure",
63
+ "phenomenon"
64
+ ];
65
+ function createField(key, options) {
66
+ return {
67
+ key,
68
+ ...options
69
+ };
70
+ }
71
+ var WORLDORBIT_OBJECT_TYPES = new Set(ALL_OBJECTS);
72
+ var WORLDORBIT_FIELD_SCHEMAS = new Map([
73
+ createField("orbit", {
74
+ kind: "string",
75
+ placement: true,
76
+ arity: "single",
77
+ objectTypes: ORBITAL_OBJECTS
78
+ }),
79
+ createField("distance", {
80
+ kind: "unit",
81
+ placement: true,
82
+ arity: "single",
83
+ objectTypes: ORBITAL_OBJECTS,
84
+ unitFamily: "distance"
85
+ }),
86
+ createField("semiMajor", {
87
+ kind: "unit",
88
+ placement: true,
89
+ arity: "single",
90
+ objectTypes: ORBITAL_OBJECTS,
91
+ unitFamily: "distance"
92
+ }),
93
+ createField("eccentricity", {
94
+ kind: "number",
95
+ placement: true,
96
+ arity: "single",
97
+ objectTypes: ORBITAL_OBJECTS
98
+ }),
99
+ createField("period", {
100
+ kind: "unit",
101
+ placement: true,
102
+ arity: "single",
103
+ objectTypes: ORBITAL_OBJECTS,
104
+ unitFamily: "duration"
105
+ }),
106
+ createField("angle", {
107
+ kind: "unit",
108
+ placement: true,
109
+ arity: "single",
110
+ objectTypes: ORBITAL_OBJECTS,
111
+ unitFamily: "angle"
112
+ }),
113
+ createField("inclination", {
114
+ kind: "unit",
115
+ placement: true,
116
+ arity: "single",
117
+ objectTypes: ORBITAL_OBJECTS,
118
+ unitFamily: "angle"
119
+ }),
120
+ createField("phase", {
121
+ kind: "unit",
122
+ placement: true,
123
+ arity: "single",
124
+ objectTypes: ORBITAL_OBJECTS,
125
+ unitFamily: "angle"
126
+ }),
127
+ createField("at", {
128
+ kind: "string",
129
+ placement: true,
130
+ arity: "single",
131
+ objectTypes: ANCHORED_OBJECTS
132
+ }),
133
+ createField("surface", {
134
+ kind: "string",
135
+ placement: true,
136
+ arity: "single",
137
+ objectTypes: ANCHORED_OBJECTS
138
+ }),
139
+ createField("free", {
140
+ kind: "string",
141
+ placement: true,
142
+ arity: "single",
143
+ objectTypes: FREE_OBJECTS
144
+ }),
145
+ createField("kind", {
146
+ kind: "string",
147
+ placement: false,
148
+ arity: "single",
149
+ objectTypes: NON_SYSTEM_OBJECTS
150
+ }),
151
+ createField("class", {
152
+ kind: "string",
153
+ placement: false,
154
+ arity: "single",
155
+ objectTypes: NON_SYSTEM_OBJECTS
156
+ }),
157
+ createField("culture", {
158
+ kind: "string",
159
+ placement: false,
160
+ arity: "single",
161
+ objectTypes: NON_SYSTEM_OBJECTS
162
+ }),
163
+ createField("tags", {
164
+ kind: "list",
165
+ placement: false,
166
+ arity: "multiple",
167
+ objectTypes: ALL_OBJECTS
168
+ }),
169
+ createField("color", {
170
+ kind: "string",
171
+ placement: false,
172
+ arity: "single",
173
+ objectTypes: ALL_OBJECTS
174
+ }),
175
+ createField("image", {
176
+ kind: "string",
177
+ placement: false,
178
+ arity: "single",
179
+ objectTypes: IMAGE_OBJECTS
180
+ }),
181
+ createField("hidden", {
182
+ kind: "boolean",
183
+ placement: false,
184
+ arity: "single",
185
+ objectTypes: ALL_OBJECTS
186
+ }),
187
+ createField("radius", {
188
+ kind: "unit",
189
+ placement: false,
190
+ arity: "single",
191
+ objectTypes: NON_SYSTEM_OBJECTS,
192
+ unitFamily: "radius"
193
+ }),
194
+ createField("mass", {
195
+ kind: "unit",
196
+ placement: false,
197
+ arity: "single",
198
+ objectTypes: NON_SYSTEM_OBJECTS,
199
+ unitFamily: "mass"
200
+ }),
201
+ createField("density", {
202
+ kind: "unit",
203
+ placement: false,
204
+ arity: "single",
205
+ objectTypes: NON_SYSTEM_OBJECTS,
206
+ unitFamily: "generic"
207
+ }),
208
+ createField("gravity", {
209
+ kind: "unit",
210
+ placement: false,
211
+ arity: "single",
212
+ objectTypes: NON_SYSTEM_OBJECTS,
213
+ unitFamily: "generic"
214
+ }),
215
+ createField("temperature", {
216
+ kind: "unit",
217
+ placement: false,
218
+ arity: "single",
219
+ objectTypes: NON_SYSTEM_OBJECTS,
220
+ unitFamily: "generic"
221
+ }),
222
+ createField("albedo", {
223
+ kind: "number",
224
+ placement: false,
225
+ arity: "single",
226
+ objectTypes: NON_SYSTEM_OBJECTS
227
+ }),
228
+ createField("atmosphere", {
229
+ kind: "string",
230
+ placement: false,
231
+ arity: "single",
232
+ objectTypes: ["planet", "moon", "asteroid", "comet", "phenomenon"]
233
+ }),
234
+ createField("inner", {
235
+ kind: "unit",
236
+ placement: false,
237
+ arity: "single",
238
+ objectTypes: ["belt", "ring", "phenomenon"],
239
+ unitFamily: "distance"
240
+ }),
241
+ createField("outer", {
242
+ kind: "unit",
243
+ placement: false,
244
+ arity: "single",
245
+ objectTypes: ["belt", "ring", "phenomenon"],
246
+ unitFamily: "distance"
247
+ }),
248
+ createField("view", {
249
+ kind: "string",
250
+ placement: false,
251
+ arity: "single",
252
+ objectTypes: ["system"]
253
+ }),
254
+ createField("scale", {
255
+ kind: "string",
256
+ placement: false,
257
+ arity: "single",
258
+ objectTypes: ["system"]
259
+ }),
260
+ createField("units", {
261
+ kind: "string",
262
+ placement: false,
263
+ arity: "single",
264
+ objectTypes: ["system"]
265
+ }),
266
+ createField("title", {
267
+ kind: "string",
268
+ placement: false,
269
+ arity: "single",
270
+ objectTypes: ["system"]
271
+ }),
272
+ createField("on", {
273
+ kind: "string",
274
+ placement: false,
275
+ arity: "single",
276
+ objectTypes: NON_SYSTEM_OBJECTS
277
+ }),
278
+ createField("source", {
279
+ kind: "string",
280
+ placement: false,
281
+ arity: "single",
282
+ objectTypes: NON_SYSTEM_OBJECTS
283
+ }),
284
+ createField("cycle", {
285
+ kind: "unit",
286
+ placement: false,
287
+ arity: "single",
288
+ objectTypes: NON_SYSTEM_OBJECTS,
289
+ unitFamily: "duration"
290
+ })
291
+ ].map((schema) => [schema.key, schema]));
292
+ var WORLDORBIT_FIELD_KEYS = new Set(WORLDORBIT_FIELD_SCHEMAS.keys());
293
+ function getFieldSchema(key) {
294
+ return WORLDORBIT_FIELD_SCHEMAS.get(key);
295
+ }
296
+ function isKnownFieldKey(key) {
297
+ return WORLDORBIT_FIELD_KEYS.has(key);
298
+ }
299
+ function supportsObjectType(schema, objectType) {
300
+ return schema.objectTypes.includes(objectType);
301
+ }
302
+ function unitFamilyAllowsUnit(family, unit) {
303
+ switch (family) {
304
+ case "distance":
305
+ return unit === null || ["au", "km", "re", "sol"].includes(unit);
306
+ case "radius":
307
+ return unit === null || ["km", "re", "sol"].includes(unit);
308
+ case "mass":
309
+ return unit === null || ["me", "sol"].includes(unit);
310
+ case "duration":
311
+ return unit === null || ["h", "d", "y"].includes(unit);
312
+ case "angle":
313
+ return unit === null || unit === "deg";
314
+ case "generic":
315
+ return true;
316
+ }
317
+ }
318
+
319
+ // packages/core/dist/tokenize.js
320
+ function tokenizeLine(input) {
321
+ return tokenizeLineDetailed(input).map((token) => token.value);
322
+ }
323
+ function tokenizeLineDetailed(input, options = {}) {
324
+ const tokens = [];
325
+ const columnOffset = options.columnOffset ?? 0;
326
+ let current = "";
327
+ let tokenColumn = null;
328
+ let tokenWasQuoted = false;
329
+ let inQuotes = false;
330
+ let quoteColumn = null;
331
+ const pushToken = () => {
332
+ if (tokenColumn === null) {
333
+ return;
334
+ }
335
+ tokens.push({
336
+ value: current,
337
+ column: tokenColumn,
338
+ quoted: tokenWasQuoted
339
+ });
340
+ current = "";
341
+ tokenColumn = null;
342
+ tokenWasQuoted = false;
343
+ };
344
+ for (let index = 0; index < input.length; index++) {
345
+ const ch = input[index];
346
+ const absoluteColumn = columnOffset + index + 1;
347
+ if (inQuotes && ch === "\\") {
348
+ const next = input[index + 1];
349
+ if (next === '"' || next === "\\") {
350
+ current += next;
351
+ index++;
352
+ continue;
353
+ }
354
+ }
355
+ if (ch === '"') {
356
+ if (!inQuotes) {
357
+ if (tokenColumn === null) {
358
+ tokenColumn = absoluteColumn;
359
+ }
360
+ tokenWasQuoted = true;
361
+ quoteColumn = absoluteColumn;
362
+ inQuotes = true;
363
+ } else {
364
+ inQuotes = false;
365
+ }
366
+ continue;
367
+ }
368
+ if (!inQuotes && /\s/.test(ch)) {
369
+ pushToken();
370
+ continue;
371
+ }
372
+ if (tokenColumn === null) {
373
+ tokenColumn = absoluteColumn;
374
+ }
375
+ current += ch;
376
+ }
377
+ if (inQuotes) {
378
+ throw new WorldOrbitError("Unclosed quote in line", options.line, quoteColumn ?? columnOffset + input.length);
379
+ }
380
+ pushToken();
381
+ return tokens;
382
+ }
383
+ function getIndent(rawLine) {
384
+ return rawLine.match(/^\s*/)?.[0].length ?? 0;
385
+ }
386
+
387
+ // packages/core/dist/parse.js
388
+ function parseWorldOrbit(source) {
389
+ const lines = source.split(/\r?\n/);
390
+ const objects = [];
391
+ let currentObject = null;
392
+ let inInfoBlock = false;
393
+ let infoIndent = null;
394
+ for (let index = 0; index < lines.length; index++) {
395
+ const rawLine = lines[index];
396
+ const lineNumber = index + 1;
397
+ if (!rawLine.trim()) {
398
+ continue;
399
+ }
400
+ const indent = getIndent(rawLine);
401
+ const tokens = tokenizeLineDetailed(rawLine.slice(indent), {
402
+ line: lineNumber,
403
+ columnOffset: indent
404
+ });
405
+ if (tokens.length === 0) {
406
+ continue;
407
+ }
408
+ if (indent === 0) {
409
+ inInfoBlock = false;
410
+ infoIndent = null;
411
+ const objectNode = parseObjectHeader(tokens, lineNumber);
412
+ objects.push(objectNode);
413
+ currentObject = objectNode;
414
+ continue;
415
+ }
416
+ if (!currentObject) {
417
+ throw new WorldOrbitError("Indented line without parent object", lineNumber, indent + 1);
418
+ }
419
+ if (tokens.length === 1 && tokens[0].value === "info") {
420
+ inInfoBlock = true;
421
+ infoIndent = indent;
422
+ continue;
423
+ }
424
+ if (inInfoBlock && indent <= (infoIndent ?? 0)) {
425
+ inInfoBlock = false;
426
+ }
427
+ if (inInfoBlock) {
428
+ currentObject.infoEntries.push(parseInfoEntry(tokens, lineNumber));
429
+ } else {
430
+ currentObject.blockFields.push(parseField(tokens, lineNumber));
431
+ }
432
+ }
433
+ return {
434
+ type: "document",
435
+ objects
436
+ };
437
+ }
438
+ function parseObjectHeader(tokens, line) {
439
+ if (tokens.length < 2) {
440
+ throw new WorldOrbitError("Invalid object declaration", line, tokens[0]?.column ?? 1);
441
+ }
442
+ const [objectTypeToken, nameToken, ...rest] = tokens;
443
+ if (!WORLDORBIT_OBJECT_TYPES.has(objectTypeToken.value)) {
444
+ throw new WorldOrbitError(`Unknown object type "${objectTypeToken.value}"`, line, objectTypeToken.column);
445
+ }
446
+ return {
447
+ type: "object",
448
+ objectType: objectTypeToken.value,
449
+ name: nameToken.value,
450
+ inlineFields: parseInlineFields(rest, line),
451
+ blockFields: [],
452
+ infoEntries: [],
453
+ location: { line, column: objectTypeToken.column }
454
+ };
455
+ }
456
+ function parseInlineFields(tokens, line) {
457
+ const fields = [];
458
+ let index = 0;
459
+ while (index < tokens.length) {
460
+ const keyToken = tokens[index];
461
+ const schema = getFieldSchema(keyToken.value);
462
+ if (!schema) {
463
+ throw new WorldOrbitError(`Unknown field "${keyToken.value}"`, line, keyToken.column);
464
+ }
465
+ index++;
466
+ const valueTokens = [];
467
+ if (schema.arity === "multiple") {
468
+ while (index < tokens.length && !isKnownFieldKey(tokens[index].value)) {
469
+ valueTokens.push(tokens[index]);
470
+ index++;
471
+ }
472
+ } else {
473
+ const nextToken = tokens[index];
474
+ if (nextToken) {
475
+ valueTokens.push(nextToken);
476
+ index++;
477
+ }
478
+ }
479
+ if (valueTokens.length === 0) {
480
+ throw new WorldOrbitError(`Missing value for field "${keyToken.value}"`, line, keyToken.column);
481
+ }
482
+ fields.push({
483
+ type: "field",
484
+ key: keyToken.value,
485
+ values: valueTokens.map((token) => token.value),
486
+ location: { line, column: keyToken.column }
487
+ });
488
+ }
489
+ return fields;
490
+ }
491
+ function parseField(tokens, line) {
492
+ if (tokens.length < 2) {
493
+ throw new WorldOrbitError("Invalid field line", line, tokens[0]?.column ?? 1);
494
+ }
495
+ if (!getFieldSchema(tokens[0].value)) {
496
+ throw new WorldOrbitError(`Unknown field "${tokens[0].value}"`, line, tokens[0].column);
497
+ }
498
+ return {
499
+ type: "field",
500
+ key: tokens[0].value,
501
+ values: tokens.slice(1).map((token) => token.value),
502
+ location: { line, column: tokens[0].column }
503
+ };
504
+ }
505
+ function parseInfoEntry(tokens, line) {
506
+ if (tokens.length < 2) {
507
+ throw new WorldOrbitError("Invalid info entry", line, tokens[0]?.column ?? 1);
508
+ }
509
+ return {
510
+ type: "info-entry",
511
+ key: tokens[0].value,
512
+ value: tokens.slice(1).map((token) => token.value).join(" "),
513
+ location: { line, column: tokens[0].column }
514
+ };
515
+ }
516
+
517
+ // packages/core/dist/normalize.js
518
+ var UNIT_PATTERN = /^(-?\d+(?:\.\d+)?)(au|km|re|sol|me|d|y|h|deg)?$/;
519
+ var BOOLEAN_VALUES = /* @__PURE__ */ new Map([
520
+ ["true", true],
521
+ ["false", false],
522
+ ["yes", true],
523
+ ["no", false]
524
+ ]);
525
+ var URL_SCHEME_PATTERN = /^[A-Za-z][A-Za-z0-9+.-]*:/;
526
+ function normalizeDocument(ast) {
527
+ let system = null;
528
+ const objects = [];
529
+ for (const node of ast.objects) {
530
+ const normalized = normalizeObject(node);
531
+ if (node.objectType === "system") {
532
+ if (system) {
533
+ throw WorldOrbitError.fromLocation("Only one system object is allowed", node.location);
534
+ }
535
+ system = normalized;
536
+ } else {
537
+ objects.push(normalized);
538
+ }
539
+ }
540
+ return {
541
+ format: "worldorbit",
542
+ version: "1.0",
543
+ system,
544
+ objects
545
+ };
546
+ }
547
+ function normalizeObject(node) {
548
+ const mergedFields = [...node.inlineFields, ...node.blockFields];
549
+ validateFieldCompatibility(node.objectType, mergedFields);
550
+ const fieldMap = collectFields(mergedFields);
551
+ const placement = extractPlacement(node.objectType, fieldMap);
552
+ const properties = normalizeProperties(fieldMap);
553
+ const info = normalizeInfo(node.infoEntries);
554
+ if (node.objectType === "system") {
555
+ return {
556
+ type: "system",
557
+ id: node.name,
558
+ properties,
559
+ info
560
+ };
561
+ }
562
+ return {
563
+ type: node.objectType,
564
+ id: node.name,
565
+ properties,
566
+ placement,
567
+ info
568
+ };
569
+ }
570
+ function validateFieldCompatibility(objectType, fields) {
571
+ for (const field of fields) {
572
+ const schema = getFieldSchema(field.key);
573
+ if (!schema) {
574
+ throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
575
+ }
576
+ if (!supportsObjectType(schema, objectType)) {
577
+ throw WorldOrbitError.fromLocation(`Field "${field.key}" is not valid on "${objectType}"`, field.location);
578
+ }
579
+ if (schema.arity === "single" && field.values.length !== 1) {
580
+ throw WorldOrbitError.fromLocation(`Field "${field.key}" expects exactly one value`, field.location);
581
+ }
582
+ }
583
+ }
584
+ function collectFields(fields) {
585
+ const map = /* @__PURE__ */ new Map();
586
+ for (const field of fields) {
587
+ if (map.has(field.key)) {
588
+ throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
589
+ }
590
+ map.set(field.key, field);
591
+ }
592
+ return map;
593
+ }
594
+ function extractPlacement(objectType, fieldMap) {
595
+ const hasOrbit = fieldMap.has("orbit");
596
+ const hasAt = fieldMap.has("at");
597
+ const hasSurface = fieldMap.has("surface");
598
+ const hasFree = fieldMap.has("free");
599
+ const count = [hasOrbit, hasAt, hasSurface, hasFree].filter(Boolean).length;
600
+ if (count > 1) {
601
+ const conflictingField = fieldMap.get("orbit") ?? fieldMap.get("at") ?? fieldMap.get("surface") ?? fieldMap.get("free");
602
+ throw WorldOrbitError.fromLocation("Object has multiple placement modes", conflictingField?.location);
603
+ }
604
+ if (objectType === "system" && count > 0) {
605
+ throw WorldOrbitError.fromLocation("System objects cannot declare placement", [...fieldMap.values()][0]?.location);
606
+ }
607
+ if (hasOrbit) {
608
+ return {
609
+ mode: "orbit",
610
+ target: singleValue(fieldMap, "orbit"),
611
+ distance: parseOptionalUnitValue(fieldMap, "distance"),
612
+ semiMajor: parseOptionalUnitValue(fieldMap, "semiMajor"),
613
+ eccentricity: parseOptionalNumber(fieldMap, "eccentricity"),
614
+ period: parseOptionalUnitValue(fieldMap, "period"),
615
+ angle: parseOptionalUnitValue(fieldMap, "angle"),
616
+ inclination: parseOptionalUnitValue(fieldMap, "inclination"),
617
+ phase: parseOptionalUnitValue(fieldMap, "phase")
618
+ };
619
+ }
620
+ if (hasAt) {
621
+ const field = getField(fieldMap, "at");
622
+ const target = singleValue(fieldMap, "at");
623
+ return {
624
+ mode: "at",
625
+ target,
626
+ reference: parseAtReference(target, field.location)
627
+ };
628
+ }
629
+ if (hasSurface) {
630
+ return {
631
+ mode: "surface",
632
+ target: singleValue(fieldMap, "surface")
633
+ };
634
+ }
635
+ if (hasFree) {
636
+ const raw = singleValue(fieldMap, "free");
637
+ const distance = tryParseUnitValue(raw);
638
+ return {
639
+ mode: "free",
640
+ distance: distance ?? void 0,
641
+ descriptor: distance ? void 0 : raw
642
+ };
643
+ }
644
+ return null;
645
+ }
646
+ function normalizeProperties(fieldMap) {
647
+ const result = {};
648
+ for (const [key, field] of fieldMap.entries()) {
649
+ const schema = getFieldSchema(key);
650
+ if (!schema || schema.placement) {
651
+ continue;
652
+ }
653
+ switch (schema.kind) {
654
+ case "list":
655
+ result[key] = field.values;
656
+ break;
657
+ case "boolean":
658
+ result[key] = parseBoolean(field);
659
+ break;
660
+ case "number":
661
+ result[key] = parseNumber(singleFieldValue(field), key, field.location);
662
+ break;
663
+ case "unit":
664
+ result[key] = parseUnitValue(singleFieldValue(field), field.location, key);
665
+ break;
666
+ case "string":
667
+ result[key] = normalizeStringValue(key, field);
668
+ break;
669
+ }
670
+ }
671
+ return result;
672
+ }
673
+ function normalizeStringValue(key, field) {
674
+ const value = field.values.join(" ").trim();
675
+ if (key === "image") {
676
+ validateImageSource(value, field.location);
677
+ }
678
+ return value;
679
+ }
680
+ function validateImageSource(value, location) {
681
+ if (!value) {
682
+ throw WorldOrbitError.fromLocation('Field "image" must not be empty', location);
683
+ }
684
+ if (value.startsWith("//")) {
685
+ throw WorldOrbitError.fromLocation('Field "image" must use a relative path, root-relative path, or an http/https URL', location);
686
+ }
687
+ const schemeMatch = value.match(URL_SCHEME_PATTERN);
688
+ if (!schemeMatch) {
689
+ return;
690
+ }
691
+ const scheme = schemeMatch[0].slice(0, -1).toLowerCase();
692
+ if (scheme !== "http" && scheme !== "https") {
693
+ throw WorldOrbitError.fromLocation(`Field "image" does not support the "${scheme}" scheme`, location);
694
+ }
695
+ }
696
+ function normalizeInfo(entries) {
697
+ const info = {};
698
+ for (const entry of entries) {
699
+ if (entry.key in info) {
700
+ throw WorldOrbitError.fromLocation(`Duplicate info key "${entry.key}"`, entry.location);
701
+ }
702
+ info[entry.key] = entry.value;
703
+ }
704
+ return info;
705
+ }
706
+ function parseAtReference(target, location) {
707
+ if (/^[A-Za-z0-9._-]+-[A-Za-z0-9._-]+:L\d+$/i.test(target)) {
708
+ throw WorldOrbitError.fromLocation(`Invalid special position "${target}"`, location);
709
+ }
710
+ const pairedMatch = target.match(/^([A-Za-z0-9._-]+)-([A-Za-z0-9._-]+):(L[1-5])$/);
711
+ if (pairedMatch) {
712
+ return {
713
+ kind: "lagrange",
714
+ primary: pairedMatch[1],
715
+ secondary: pairedMatch[2],
716
+ point: pairedMatch[3]
717
+ };
718
+ }
719
+ const simpleMatch = target.match(/^([A-Za-z0-9._-]+):(L[1-5])$/);
720
+ if (simpleMatch) {
721
+ return {
722
+ kind: "lagrange",
723
+ primary: simpleMatch[1],
724
+ secondary: null,
725
+ point: simpleMatch[2]
726
+ };
727
+ }
728
+ if (/^[A-Za-z0-9._-]+:L\d+$/i.test(target)) {
729
+ throw WorldOrbitError.fromLocation(`Invalid special position "${target}"`, location);
730
+ }
731
+ const anchorMatch = target.match(/^([A-Za-z0-9._-]+):([A-Za-z0-9._-]+)$/);
732
+ if (anchorMatch) {
733
+ return {
734
+ kind: "anchor",
735
+ objectId: anchorMatch[1],
736
+ anchor: anchorMatch[2]
737
+ };
738
+ }
739
+ return {
740
+ kind: "named",
741
+ name: target
742
+ };
743
+ }
744
+ function parseUnitValue(input, location, fieldKey) {
745
+ const match = input.match(UNIT_PATTERN);
746
+ if (!match) {
747
+ throw WorldOrbitError.fromLocation(`Invalid unit value "${input}"`, location);
748
+ }
749
+ const unitValue = {
750
+ value: Number(match[1]),
751
+ unit: match[2] ?? null
752
+ };
753
+ if (fieldKey) {
754
+ const schema = getFieldSchema(fieldKey);
755
+ if (schema?.unitFamily && !unitFamilyAllowsUnit(schema.unitFamily, unitValue.unit)) {
756
+ throw WorldOrbitError.fromLocation(`Unit "${unitValue.unit ?? "none"}" is not valid for "${fieldKey}"`, location);
757
+ }
758
+ }
759
+ return unitValue;
760
+ }
761
+ function tryParseUnitValue(input) {
762
+ const match = input.match(UNIT_PATTERN);
763
+ if (!match) {
764
+ return null;
765
+ }
766
+ return {
767
+ value: Number(match[1]),
768
+ unit: match[2] ?? null
769
+ };
770
+ }
771
+ function parseOptionalUnitValue(fieldMap, key) {
772
+ if (!fieldMap.has(key)) {
773
+ return void 0;
774
+ }
775
+ const field = getField(fieldMap, key);
776
+ return parseUnitValue(singleFieldValue(field), field.location, key);
777
+ }
778
+ function parseOptionalNumber(fieldMap, key) {
779
+ if (!fieldMap.has(key)) {
780
+ return void 0;
781
+ }
782
+ const field = getField(fieldMap, key);
783
+ return parseNumber(singleFieldValue(field), key, field.location);
784
+ }
785
+ function parseNumber(input, key, location) {
786
+ const value = Number(input);
787
+ if (!Number.isFinite(value)) {
788
+ throw WorldOrbitError.fromLocation(`Invalid numeric value "${input}" for "${key}"`, location);
789
+ }
790
+ return value;
791
+ }
792
+ function parseBoolean(field) {
793
+ const rawValue = singleFieldValue(field).toLowerCase();
794
+ const parsed = BOOLEAN_VALUES.get(rawValue);
795
+ if (parsed === void 0) {
796
+ throw WorldOrbitError.fromLocation(`Invalid boolean value "${rawValue}" for "${field.key}"`, field.location);
797
+ }
798
+ return parsed;
799
+ }
800
+ function getField(fieldMap, key) {
801
+ const field = fieldMap.get(key);
802
+ if (!field) {
803
+ throw new WorldOrbitError(`Missing value for key "${key}"`);
804
+ }
805
+ return field;
806
+ }
807
+ function singleValue(fieldMap, key) {
808
+ return singleFieldValue(getField(fieldMap, key));
809
+ }
810
+ function singleFieldValue(field) {
811
+ if (field.values.length !== 1) {
812
+ throw WorldOrbitError.fromLocation(`Field "${field.key}" expects exactly one value`, field.location);
813
+ }
814
+ return field.values[0];
815
+ }
816
+
817
+ // packages/core/dist/validate.js
818
+ var SURFACE_TARGET_TYPES = /* @__PURE__ */ new Set([
819
+ "star",
820
+ "planet",
821
+ "moon",
822
+ "asteroid",
823
+ "comet"
824
+ ]);
825
+ function validateDocument(doc) {
826
+ const knownIds = /* @__PURE__ */ new Set();
827
+ const objectMap = /* @__PURE__ */ new Map();
828
+ for (const obj of doc.objects) {
829
+ if (knownIds.has(obj.id)) {
830
+ throw new WorldOrbitError(`Duplicate object id "${obj.id}"`);
831
+ }
832
+ knownIds.add(obj.id);
833
+ objectMap.set(obj.id, obj);
834
+ }
835
+ for (const obj of doc.objects) {
836
+ if (!obj.placement) {
837
+ continue;
838
+ }
839
+ if (obj.placement.mode === "orbit" || obj.placement.mode === "surface") {
840
+ if (!knownIds.has(obj.placement.target)) {
841
+ throw new WorldOrbitError(`Unknown placement target "${obj.placement.target}" on "${obj.id}"`);
842
+ }
843
+ }
844
+ if (obj.placement.mode === "surface") {
845
+ const target = objectMap.get(obj.placement.target);
846
+ if (!target || !SURFACE_TARGET_TYPES.has(target.type)) {
847
+ throw new WorldOrbitError(`Surface target "${obj.placement.target}" on "${obj.id}" is not surface-capable`);
848
+ }
849
+ }
850
+ if (obj.placement.mode === "at") {
851
+ if (obj.placement.reference.kind === "lagrange") {
852
+ validateLagrangeReference(obj, obj.placement.reference, knownIds);
853
+ }
854
+ if (obj.placement.reference.kind === "anchor") {
855
+ validateAnchorReference(obj, obj.placement.reference, knownIds);
856
+ }
857
+ }
858
+ }
859
+ }
860
+ function validateLagrangeReference(obj, reference, knownIds) {
861
+ if (!knownIds.has(reference.primary)) {
862
+ throw new WorldOrbitError(`Unknown Lagrange reference "${reference.primary}" on "${obj.id}"`);
863
+ }
864
+ if (reference.secondary && !knownIds.has(reference.secondary)) {
865
+ throw new WorldOrbitError(`Unknown Lagrange reference "${reference.secondary}" on "${obj.id}"`);
866
+ }
867
+ }
868
+ function validateAnchorReference(obj, reference, knownIds) {
869
+ if (!knownIds.has(reference.objectId)) {
870
+ throw new WorldOrbitError(`Unknown anchor target "${reference.objectId}" on "${obj.id}"`);
871
+ }
872
+ }
873
+
874
+ // packages/core/dist/diagnostics.js
875
+ function createDiagnostic(diagnostic) {
876
+ return { ...diagnostic };
877
+ }
878
+ function diagnosticFromError(error, source, code = `${source}.failed`) {
879
+ if (error instanceof WorldOrbitError) {
880
+ return {
881
+ code,
882
+ severity: "error",
883
+ source,
884
+ message: error.message,
885
+ line: error.line,
886
+ column: error.column
887
+ };
888
+ }
889
+ if (error instanceof Error) {
890
+ return {
891
+ code,
892
+ severity: "error",
893
+ source,
894
+ message: error.message
895
+ };
896
+ }
897
+ return {
898
+ code,
899
+ severity: "error",
900
+ source,
901
+ message: String(error)
902
+ };
903
+ }
904
+ function parseWithDiagnostics(source) {
905
+ let ast;
906
+ try {
907
+ ast = parseWorldOrbit(source);
908
+ } catch (error) {
909
+ const diagnostic = diagnosticFromError(error, "parse");
910
+ return {
911
+ ok: false,
912
+ value: null,
913
+ diagnostics: [diagnostic]
914
+ };
915
+ }
916
+ let document;
917
+ try {
918
+ document = normalizeDocument(ast);
919
+ } catch (error) {
920
+ return {
921
+ ok: false,
922
+ value: null,
923
+ diagnostics: [diagnosticFromError(error, "normalize")]
924
+ };
925
+ }
926
+ try {
927
+ validateDocument(document);
928
+ } catch (error) {
929
+ return {
930
+ ok: false,
931
+ value: null,
932
+ diagnostics: [diagnosticFromError(error, "validate")]
933
+ };
934
+ }
935
+ return {
936
+ ok: true,
937
+ value: {
938
+ ast,
939
+ document
940
+ },
941
+ diagnostics: []
942
+ };
943
+ }
944
+ function normalizeWithDiagnostics(ast) {
945
+ try {
946
+ return {
947
+ ok: true,
948
+ value: normalizeDocument(ast),
949
+ diagnostics: []
950
+ };
951
+ } catch (error) {
952
+ return {
953
+ ok: false,
954
+ value: null,
955
+ diagnostics: [diagnosticFromError(error, "normalize")]
956
+ };
957
+ }
958
+ }
959
+ function validateDocumentWithDiagnostics(document) {
960
+ try {
961
+ validateDocument(document);
962
+ return {
963
+ ok: true,
964
+ value: document,
965
+ diagnostics: []
966
+ };
967
+ } catch (error) {
968
+ return {
969
+ ok: false,
970
+ value: null,
971
+ diagnostics: [diagnosticFromError(error, "validate")]
972
+ };
973
+ }
974
+ }
975
+
976
+ // packages/core/dist/scene.js
977
+ var AU_IN_KM = 1495978707e-1;
978
+ var EARTH_RADIUS_IN_KM = 6371;
979
+ var SOLAR_RADIUS_IN_KM = 695700;
980
+ var ISO_FLATTENING = 0.68;
981
+ var MIN_ISO_MINOR_SCALE = 0.2;
982
+ var ARC_SAMPLE_COUNT = 28;
983
+ function renderDocumentToScene(document, options = {}) {
984
+ const frame = resolveSceneFrame(options);
985
+ const width = frame.width;
986
+ const height = frame.height;
987
+ const padding = frame.padding;
988
+ const layoutPreset = resolveLayoutPreset(document);
989
+ const projection = resolveProjection(document, options.projection);
990
+ const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
991
+ const spacingFactor = layoutPresetSpacing(layoutPreset);
992
+ const systemId = document.system?.id ?? null;
993
+ const objectMap = new Map(document.objects.map((object) => [object.id, object]));
994
+ const relationships = buildSceneRelationships(document.objects, objectMap);
995
+ const positions = /* @__PURE__ */ new Map();
996
+ const orbitDrafts = [];
997
+ const leaderDrafts = [];
998
+ const rootObjects = [];
999
+ const freeObjects = [];
1000
+ const atObjects = [];
1001
+ const surfaceChildren = /* @__PURE__ */ new Map();
1002
+ const orbitChildren = /* @__PURE__ */ new Map();
1003
+ for (const object of document.objects) {
1004
+ const placement = object.placement;
1005
+ if (!placement) {
1006
+ rootObjects.push(object);
1007
+ continue;
1008
+ }
1009
+ if (placement.mode === "orbit") {
1010
+ pushGrouped(orbitChildren, placement.target, object);
1011
+ continue;
1012
+ }
1013
+ if (placement.mode === "surface") {
1014
+ pushGrouped(surfaceChildren, placement.target, object);
1015
+ continue;
1016
+ }
1017
+ if (placement.mode === "at") {
1018
+ atObjects.push(object);
1019
+ continue;
1020
+ }
1021
+ freeObjects.push(object);
1022
+ }
1023
+ const centerX = freeObjects.length > 0 ? width * 0.42 : width / 2;
1024
+ const centerY = height / 2;
1025
+ const context = {
1026
+ orbitChildren,
1027
+ surfaceChildren,
1028
+ objectMap,
1029
+ spacingFactor,
1030
+ projection,
1031
+ scaleModel
1032
+ };
1033
+ const primaryRoot = rootObjects.find((object) => object.type === "star") ?? rootObjects[0] ?? null;
1034
+ if (primaryRoot) {
1035
+ placeObject(primaryRoot, centerX, centerY, 0, positions, orbitDrafts, leaderDrafts, context);
1036
+ }
1037
+ const secondaryRoots = rootObjects.filter((object) => object.id !== primaryRoot?.id);
1038
+ if (secondaryRoots.length > 0) {
1039
+ const rootRingRadius = Math.min(width, height) * 0.28 * spacingFactor * scaleModel.orbitDistanceMultiplier;
1040
+ secondaryRoots.forEach((object, index) => {
1041
+ const angle = angleForIndex(index, secondaryRoots.length, -Math.PI / 2);
1042
+ const offset = projectPolarOffset(angle, rootRingRadius, projection, 1);
1043
+ placeObject(object, centerX + offset.x, centerY + offset.y, 0, positions, orbitDrafts, leaderDrafts, context);
1044
+ });
1045
+ }
1046
+ freeObjects.forEach((object, index) => {
1047
+ const x = width - padding - 140 - freePlacementOffsetPx(object.placement?.mode === "free" ? object.placement.distance : void 0, scaleModel);
1048
+ const rowStep = Math.max(76, (height - padding * 2 - 180) / Math.max(1, freeObjects.length) * spacingFactor) * scaleModel.freePlacementMultiplier;
1049
+ const y = padding + 92 + index * rowStep;
1050
+ positions.set(object.id, {
1051
+ object,
1052
+ x,
1053
+ y,
1054
+ radius: visualRadiusFor(object, 0, scaleModel),
1055
+ sortKey: computeSortKey(x, y, 0)
1056
+ });
1057
+ leaderDrafts.push({
1058
+ object,
1059
+ groupId: relationships.groupIds.get(object.id) ?? null,
1060
+ x1: x - 60,
1061
+ y1: y,
1062
+ x2: x - 18,
1063
+ y2: y,
1064
+ mode: "free"
1065
+ });
1066
+ placeOrbitingChildren(object, positions, orbitDrafts, leaderDrafts, context, 1);
1067
+ });
1068
+ atObjects.forEach((object, index) => {
1069
+ if (positions.has(object.id) || !object.placement || object.placement.mode !== "at") {
1070
+ return;
1071
+ }
1072
+ const resolved = resolveAtPosition(object.placement.reference, positions, objectMap, index, atObjects.length, width, height, padding, context);
1073
+ positions.set(object.id, {
1074
+ object,
1075
+ x: resolved.x,
1076
+ y: resolved.y,
1077
+ radius: visualRadiusFor(object, 2, scaleModel),
1078
+ sortKey: computeSortKey(resolved.x, resolved.y, 2),
1079
+ anchorX: resolved.anchorX,
1080
+ anchorY: resolved.anchorY
1081
+ });
1082
+ if (resolved.anchorX !== void 0 && resolved.anchorY !== void 0) {
1083
+ leaderDrafts.push({
1084
+ object,
1085
+ groupId: relationships.groupIds.get(object.id) ?? null,
1086
+ x1: resolved.anchorX,
1087
+ y1: resolved.anchorY,
1088
+ x2: resolved.x,
1089
+ y2: resolved.y,
1090
+ mode: "at"
1091
+ });
1092
+ }
1093
+ placeOrbitingChildren(object, positions, orbitDrafts, leaderDrafts, context, 2);
1094
+ });
1095
+ const objects = [...positions.values()].map((position) => createSceneObject(position, scaleModel, relationships));
1096
+ const orbitVisuals = orbitDrafts.map((draft) => createOrbitVisual(draft, relationships.groupIds.get(draft.object.id) ?? null));
1097
+ const leaders = leaderDrafts.map((draft) => createLeaderLine(draft));
1098
+ const labels = createSceneLabels(objects, height, scaleModel.labelMultiplier);
1099
+ const layers = createSceneLayers(orbitVisuals, leaders, objects, labels);
1100
+ const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships);
1101
+ const viewpoints = createSceneViewpoints(document, projection, frame.preset, relationships, objectMap);
1102
+ const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels);
1103
+ return {
1104
+ width,
1105
+ height,
1106
+ padding,
1107
+ renderPreset: frame.preset,
1108
+ projection,
1109
+ scaleModel,
1110
+ title: String(document.system?.properties.title ?? document.system?.id ?? "WorldOrbit") || "WorldOrbit",
1111
+ subtitle: `${capitalizeLabel(projection)} view - ${capitalizeLabel(layoutPreset)} layout`,
1112
+ systemId,
1113
+ viewMode: projection,
1114
+ layoutPreset,
1115
+ metadata: {
1116
+ format: document.format,
1117
+ version: document.version,
1118
+ view: projection,
1119
+ scale: String(document.system?.properties.scale ?? layoutPreset),
1120
+ units: String(document.system?.properties.units ?? "mixed"),
1121
+ preset: frame.preset ?? "custom"
1122
+ },
1123
+ contentBounds,
1124
+ layers,
1125
+ groups,
1126
+ viewpoints,
1127
+ objects,
1128
+ orbitVisuals,
1129
+ leaders,
1130
+ labels
1131
+ };
1132
+ }
1133
+ function rotatePoint(point, center, rotationDeg) {
1134
+ const radians = degreesToRadians(rotationDeg);
1135
+ const cos = Math.cos(radians);
1136
+ const sin = Math.sin(radians);
1137
+ const dx = point.x - center.x;
1138
+ const dy = point.y - center.y;
1139
+ return {
1140
+ x: center.x + dx * cos - dy * sin,
1141
+ y: center.y + dx * sin + dy * cos
1142
+ };
1143
+ }
1144
+ function resolveLayoutPreset(document) {
1145
+ const rawScale = String(document.system?.properties.scale ?? "balanced").toLowerCase();
1146
+ switch (rawScale) {
1147
+ case "compressed":
1148
+ case "compact":
1149
+ return "compact";
1150
+ case "expanded":
1151
+ case "presentation":
1152
+ return "presentation";
1153
+ default:
1154
+ return "balanced";
1155
+ }
1156
+ }
1157
+ function resolveSceneFrame(options) {
1158
+ const defaults = scenePresetDefaults(options.preset);
1159
+ return {
1160
+ width: options.width ?? defaults.width,
1161
+ height: options.height ?? defaults.height,
1162
+ padding: options.padding ?? defaults.padding,
1163
+ preset: options.preset ?? null
1164
+ };
1165
+ }
1166
+ function scenePresetDefaults(preset) {
1167
+ switch (preset) {
1168
+ case "presentation":
1169
+ return { width: 1440, height: 900, padding: 88 };
1170
+ case "atlas-card":
1171
+ return { width: 960, height: 560, padding: 56 };
1172
+ case "markdown":
1173
+ return { width: 920, height: 540, padding: 48 };
1174
+ case "diagram":
1175
+ default:
1176
+ return { width: 1200, height: 780, padding: 72 };
1177
+ }
1178
+ }
1179
+ function resolveProjection(document, projection) {
1180
+ if (projection === "topdown" || projection === "isometric") {
1181
+ return projection;
1182
+ }
1183
+ return String(document.system?.properties.view ?? "topdown").toLowerCase() === "isometric" ? "isometric" : "topdown";
1184
+ }
1185
+ function resolveScaleModel(layoutPreset, overrides) {
1186
+ const defaults = defaultScaleModel(layoutPreset);
1187
+ return {
1188
+ ...defaults,
1189
+ ...overrides
1190
+ };
1191
+ }
1192
+ function defaultScaleModel(layoutPreset) {
1193
+ switch (layoutPreset) {
1194
+ case "compact":
1195
+ return {
1196
+ orbitDistanceMultiplier: 0.84,
1197
+ bodyRadiusMultiplier: 0.92,
1198
+ labelMultiplier: 0.9,
1199
+ freePlacementMultiplier: 0.9,
1200
+ ringThicknessMultiplier: 0.92,
1201
+ minBodyRadius: 4,
1202
+ maxBodyRadius: 36
1203
+ };
1204
+ case "presentation":
1205
+ return {
1206
+ orbitDistanceMultiplier: 1.2,
1207
+ bodyRadiusMultiplier: 1.18,
1208
+ labelMultiplier: 1.08,
1209
+ freePlacementMultiplier: 1.05,
1210
+ ringThicknessMultiplier: 1.16,
1211
+ minBodyRadius: 5,
1212
+ maxBodyRadius: 48
1213
+ };
1214
+ default:
1215
+ return {
1216
+ orbitDistanceMultiplier: 1,
1217
+ bodyRadiusMultiplier: 1,
1218
+ labelMultiplier: 1,
1219
+ freePlacementMultiplier: 1,
1220
+ ringThicknessMultiplier: 1,
1221
+ minBodyRadius: 4,
1222
+ maxBodyRadius: 40
1223
+ };
1224
+ }
1225
+ }
1226
+ function layoutPresetSpacing(layoutPreset) {
1227
+ switch (layoutPreset) {
1228
+ case "compact":
1229
+ return 0.84;
1230
+ case "presentation":
1231
+ return 1.2;
1232
+ default:
1233
+ return 1;
1234
+ }
1235
+ }
1236
+ function createSceneObject(position, scaleModel, relationships) {
1237
+ const { object, x, y, radius, sortKey, anchorX, anchorY } = position;
1238
+ return {
1239
+ renderId: createRenderId(object.id),
1240
+ objectId: object.id,
1241
+ object,
1242
+ parentId: relationships.parentIds.get(object.id) ?? null,
1243
+ ancestorIds: relationships.ancestorIds.get(object.id) ?? [],
1244
+ childIds: relationships.childIds.get(object.id) ?? [],
1245
+ groupId: relationships.groupIds.get(object.id) ?? null,
1246
+ x,
1247
+ y,
1248
+ radius,
1249
+ visualRadius: visualExtentForObject(object, radius, scaleModel),
1250
+ sortKey,
1251
+ anchorX,
1252
+ anchorY,
1253
+ label: object.id,
1254
+ secondaryLabel: object.type === "structure" ? String(object.properties.kind ?? object.type) : object.type,
1255
+ fillColor: customColorFor(object.properties.color),
1256
+ imageHref: typeof object.properties.image === "string" && object.properties.image.trim() ? object.properties.image : void 0,
1257
+ hidden: object.properties.hidden === true
1258
+ };
1259
+ }
1260
+ function createOrbitVisual(draft, groupId) {
1261
+ return {
1262
+ renderId: `${createRenderId(draft.object.id)}-orbit`,
1263
+ objectId: draft.object.id,
1264
+ object: draft.object,
1265
+ parentId: draft.parentId,
1266
+ groupId,
1267
+ kind: draft.kind,
1268
+ cx: draft.cx,
1269
+ cy: draft.cy,
1270
+ radius: draft.radius,
1271
+ rx: draft.rx,
1272
+ ry: draft.ry,
1273
+ rotationDeg: draft.rotationDeg,
1274
+ band: draft.band,
1275
+ bandThickness: draft.bandThickness,
1276
+ frontArcPath: draft.frontArcPath,
1277
+ backArcPath: draft.backArcPath,
1278
+ hidden: draft.object.properties.hidden === true
1279
+ };
1280
+ }
1281
+ function createLeaderLine(draft) {
1282
+ return {
1283
+ renderId: `${createRenderId(draft.object.id)}-leader-${draft.mode}`,
1284
+ objectId: draft.object.id,
1285
+ object: draft.object,
1286
+ groupId: draft.groupId,
1287
+ x1: draft.x1,
1288
+ y1: draft.y1,
1289
+ x2: draft.x2,
1290
+ y2: draft.y2,
1291
+ mode: draft.mode,
1292
+ hidden: draft.object.properties.hidden === true
1293
+ };
1294
+ }
1295
+ function createSceneLabels(objects, sceneHeight, labelMultiplier) {
1296
+ const labels = [];
1297
+ const occupied = [];
1298
+ const visibleObjects = [...objects].filter((object) => !object.hidden).sort((left, right) => left.sortKey - right.sortKey);
1299
+ for (const object of visibleObjects) {
1300
+ const direction = object.y > sceneHeight * 0.62 ? -1 : 1;
1301
+ const labelHalfWidth = estimateLabelHalfWidth(object, labelMultiplier);
1302
+ let labelY = object.y + direction * (object.radius + 18 * labelMultiplier);
1303
+ let secondaryY = labelY + direction * (16 * labelMultiplier);
1304
+ let bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
1305
+ let attempts = 0;
1306
+ while (occupied.some((entry) => rectsOverlap(entry, bounds)) && attempts < 10) {
1307
+ labelY += direction * 14 * labelMultiplier;
1308
+ secondaryY += direction * 14 * labelMultiplier;
1309
+ bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
1310
+ attempts += 1;
1311
+ }
1312
+ occupied.push(bounds);
1313
+ labels.push({
1314
+ renderId: `${object.renderId}-label`,
1315
+ objectId: object.objectId,
1316
+ object: object.object,
1317
+ groupId: object.groupId,
1318
+ label: object.label,
1319
+ secondaryLabel: object.secondaryLabel,
1320
+ x: object.x,
1321
+ y: labelY,
1322
+ secondaryY,
1323
+ textAnchor: "middle",
1324
+ direction: direction < 0 ? "above" : "below",
1325
+ hidden: object.hidden
1326
+ });
1327
+ }
1328
+ return labels;
1329
+ }
1330
+ function createSceneLayers(orbitVisuals, leaders, objects, labels) {
1331
+ const backOrbitIds = orbitVisuals.filter((visual) => !visual.hidden && Boolean(visual.backArcPath)).map((visual) => visual.renderId);
1332
+ const frontOrbitIds = orbitVisuals.filter((visual) => !visual.hidden).map((visual) => visual.renderId);
1333
+ return [
1334
+ { id: "background", renderIds: ["wo-bg", "wo-bg-glow", "wo-grid"] },
1335
+ {
1336
+ id: "guides",
1337
+ renderIds: leaders.filter((leader) => !leader.hidden).map((leader) => leader.renderId)
1338
+ },
1339
+ { id: "orbits-back", renderIds: backOrbitIds },
1340
+ { id: "orbits-front", renderIds: frontOrbitIds },
1341
+ {
1342
+ id: "objects",
1343
+ renderIds: objects.filter((object) => !object.hidden).map((object) => object.renderId)
1344
+ },
1345
+ {
1346
+ id: "labels",
1347
+ renderIds: labels.filter((label) => !label.hidden).map((label) => label.renderId)
1348
+ },
1349
+ { id: "metadata", renderIds: ["wo-title", "wo-subtitle", "wo-meta"] }
1350
+ ];
1351
+ }
1352
+ function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships) {
1353
+ const groups = /* @__PURE__ */ new Map();
1354
+ const ensureGroup = (groupId) => {
1355
+ if (!groupId) {
1356
+ return null;
1357
+ }
1358
+ const existing = groups.get(groupId);
1359
+ if (existing) {
1360
+ return existing;
1361
+ }
1362
+ const rootObjectId = relationships.groupRoots.get(groupId) ?? null;
1363
+ const created = {
1364
+ renderId: groupId,
1365
+ rootObjectId,
1366
+ label: rootObjectId ?? groupId,
1367
+ objectIds: [],
1368
+ orbitIds: [],
1369
+ labelIds: [],
1370
+ leaderIds: [],
1371
+ contentBounds: createBounds(0, 0, 0, 0)
1372
+ };
1373
+ groups.set(groupId, created);
1374
+ return created;
1375
+ };
1376
+ for (const object of objects) {
1377
+ const group = ensureGroup(object.groupId);
1378
+ if (group && !object.hidden) {
1379
+ group.objectIds.push(object.objectId);
1380
+ }
1381
+ }
1382
+ for (const orbit of orbitVisuals) {
1383
+ const group = ensureGroup(orbit.groupId);
1384
+ if (group && !orbit.hidden) {
1385
+ group.orbitIds.push(orbit.objectId);
1386
+ }
1387
+ }
1388
+ for (const leader of leaders) {
1389
+ const group = ensureGroup(leader.groupId);
1390
+ if (group && !leader.hidden) {
1391
+ group.leaderIds.push(leader.objectId);
1392
+ }
1393
+ }
1394
+ for (const label of labels) {
1395
+ const group = ensureGroup(label.groupId);
1396
+ if (group && !label.hidden) {
1397
+ group.labelIds.push(label.objectId);
1398
+ }
1399
+ }
1400
+ for (const group of groups.values()) {
1401
+ group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels);
1402
+ }
1403
+ return [...groups.values()].sort((left, right) => left.label.localeCompare(right.label));
1404
+ }
1405
+ function createSceneViewpoints(document, projection, preset, relationships, objectMap) {
1406
+ const generatedOverview = createGeneratedOverviewViewpoint(document, projection, preset);
1407
+ const drafts = /* @__PURE__ */ new Map();
1408
+ for (const [key, value] of Object.entries(document.system?.info ?? {})) {
1409
+ if (!key.startsWith("viewpoint.")) {
1410
+ continue;
1411
+ }
1412
+ const [prefix, rawId, ...fieldParts] = key.split(".");
1413
+ if (prefix !== "viewpoint" || !rawId || fieldParts.length === 0) {
1414
+ continue;
1415
+ }
1416
+ const id = normalizeViewpointId(rawId);
1417
+ if (!id) {
1418
+ continue;
1419
+ }
1420
+ const field = fieldParts.join(".").toLowerCase();
1421
+ const draft = drafts.get(id) ?? { id };
1422
+ applyViewpointField(draft, field, value, projection, preset, relationships, objectMap);
1423
+ drafts.set(id, draft);
1424
+ }
1425
+ const viewpoints = [...drafts.values()].map((draft) => finalizeViewpointDraft(draft, projection, preset, objectMap)).filter(Boolean);
1426
+ const overviewIndex = viewpoints.findIndex((viewpoint) => viewpoint.id === generatedOverview.id);
1427
+ if (overviewIndex >= 0) {
1428
+ viewpoints.splice(overviewIndex, 1, {
1429
+ ...generatedOverview,
1430
+ ...viewpoints[overviewIndex],
1431
+ layers: {
1432
+ ...generatedOverview.layers,
1433
+ ...viewpoints[overviewIndex].layers
1434
+ },
1435
+ filter: viewpoints[overviewIndex].filter ?? generatedOverview.filter,
1436
+ generated: false
1437
+ });
1438
+ } else {
1439
+ viewpoints.unshift(generatedOverview);
1440
+ }
1441
+ return viewpoints.sort((left, right) => {
1442
+ if (left.id === "overview")
1443
+ return -1;
1444
+ if (right.id === "overview")
1445
+ return 1;
1446
+ return left.label.localeCompare(right.label);
1447
+ });
1448
+ }
1449
+ function createGeneratedOverviewViewpoint(document, projection, preset) {
1450
+ const label = document.system?.properties.title ? `${String(document.system.properties.title)} Overview` : "Overview";
1451
+ return {
1452
+ id: "overview",
1453
+ label,
1454
+ summary: "Fit the whole system with the current atlas defaults.",
1455
+ objectId: null,
1456
+ selectedObjectId: null,
1457
+ projection,
1458
+ preset,
1459
+ rotationDeg: 0,
1460
+ scale: null,
1461
+ layers: {},
1462
+ filter: null,
1463
+ generated: true
1464
+ };
1465
+ }
1466
+ function applyViewpointField(draft, field, value, projection, preset, relationships, objectMap) {
1467
+ const normalizedValue = value.trim();
1468
+ switch (field) {
1469
+ case "label":
1470
+ case "title":
1471
+ if (normalizedValue) {
1472
+ draft.label = normalizedValue;
1473
+ }
1474
+ return;
1475
+ case "summary":
1476
+ case "description":
1477
+ if (normalizedValue) {
1478
+ draft.summary = normalizedValue;
1479
+ }
1480
+ return;
1481
+ case "focus":
1482
+ case "object":
1483
+ if (normalizedValue) {
1484
+ draft.focus = normalizedValue;
1485
+ }
1486
+ return;
1487
+ case "select":
1488
+ case "selection":
1489
+ if (normalizedValue) {
1490
+ draft.select = normalizedValue;
1491
+ }
1492
+ return;
1493
+ case "projection":
1494
+ case "view":
1495
+ draft.projection = parseViewProjection(normalizedValue) ?? projection;
1496
+ return;
1497
+ case "preset":
1498
+ draft.preset = parseRenderPreset(normalizedValue) ?? preset;
1499
+ return;
1500
+ case "rotation":
1501
+ case "angle":
1502
+ draft.rotationDeg = parseFiniteNumber(normalizedValue) ?? draft.rotationDeg ?? 0;
1503
+ return;
1504
+ case "zoom":
1505
+ case "scale":
1506
+ draft.scale = parsePositiveNumber(normalizedValue);
1507
+ return;
1508
+ case "layers":
1509
+ draft.layers = parseViewpointLayers(normalizedValue);
1510
+ return;
1511
+ case "query":
1512
+ draft.filter = {
1513
+ ...draft.filter ?? createEmptyViewpointFilter(),
1514
+ query: normalizedValue || null
1515
+ };
1516
+ return;
1517
+ case "types":
1518
+ case "objecttypes":
1519
+ draft.filter = {
1520
+ ...draft.filter ?? createEmptyViewpointFilter(),
1521
+ objectTypes: parseViewpointObjectTypes(normalizedValue)
1522
+ };
1523
+ return;
1524
+ case "tags":
1525
+ draft.filter = {
1526
+ ...draft.filter ?? createEmptyViewpointFilter(),
1527
+ tags: splitListValue(normalizedValue)
1528
+ };
1529
+ return;
1530
+ case "groups":
1531
+ draft.filter = {
1532
+ ...draft.filter ?? createEmptyViewpointFilter(),
1533
+ groupIds: parseViewpointGroups(normalizedValue, relationships, objectMap)
1534
+ };
1535
+ return;
1536
+ }
1537
+ }
1538
+ function finalizeViewpointDraft(draft, projection, preset, objectMap) {
1539
+ const objectId = draft.focus && objectMap.has(draft.focus) ? draft.focus : null;
1540
+ const selectedObjectId = draft.select && objectMap.has(draft.select) ? draft.select : objectId;
1541
+ const filter = normalizeViewpointFilter(draft.filter);
1542
+ const label = draft.label?.trim() || humanizeIdentifier(draft.id);
1543
+ return {
1544
+ id: draft.id,
1545
+ label,
1546
+ summary: draft.summary?.trim() || createViewpointSummary(label, objectId, filter),
1547
+ objectId,
1548
+ selectedObjectId,
1549
+ projection: draft.projection ?? projection,
1550
+ preset: draft.preset ?? preset,
1551
+ rotationDeg: draft.rotationDeg ?? 0,
1552
+ scale: draft.scale ?? null,
1553
+ layers: draft.layers ?? {},
1554
+ filter,
1555
+ generated: false
1556
+ };
1557
+ }
1558
+ function createEmptyViewpointFilter() {
1559
+ return {
1560
+ query: null,
1561
+ objectTypes: [],
1562
+ tags: [],
1563
+ groupIds: []
1564
+ };
1565
+ }
1566
+ function normalizeViewpointFilter(filter) {
1567
+ if (!filter) {
1568
+ return null;
1569
+ }
1570
+ const normalized = {
1571
+ query: filter.query?.trim() || null,
1572
+ objectTypes: [...new Set(filter.objectTypes)],
1573
+ tags: [...new Set(filter.tags)],
1574
+ groupIds: [...new Set(filter.groupIds)]
1575
+ };
1576
+ return normalized.query || normalized.objectTypes.length > 0 || normalized.tags.length > 0 || normalized.groupIds.length > 0 ? normalized : null;
1577
+ }
1578
+ function parseViewProjection(value) {
1579
+ return value.toLowerCase() === "isometric" ? "isometric" : value.toLowerCase() === "topdown" ? "topdown" : null;
1580
+ }
1581
+ function parseRenderPreset(value) {
1582
+ const normalized = value.toLowerCase();
1583
+ if (normalized === "diagram" || normalized === "presentation" || normalized === "atlas-card" || normalized === "markdown") {
1584
+ return normalized;
1585
+ }
1586
+ return null;
1587
+ }
1588
+ function parseFiniteNumber(value) {
1589
+ const parsed = Number(value);
1590
+ return Number.isFinite(parsed) ? parsed : null;
1591
+ }
1592
+ function parsePositiveNumber(value) {
1593
+ const parsed = parseFiniteNumber(value);
1594
+ return parsed !== null && parsed > 0 ? parsed : null;
1595
+ }
1596
+ function parseViewpointLayers(value) {
1597
+ const next = {};
1598
+ for (const token of splitListValue(value)) {
1599
+ const enabled = !token.startsWith("-") && !token.startsWith("!");
1600
+ const rawLayer = token.replace(/^[-!]+/, "").toLowerCase();
1601
+ if (rawLayer === "orbits") {
1602
+ next["orbits-back"] = enabled;
1603
+ next["orbits-front"] = enabled;
1604
+ continue;
1605
+ }
1606
+ if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
1607
+ next[rawLayer] = enabled;
1608
+ }
1609
+ }
1610
+ return next;
1611
+ }
1612
+ function parseViewpointObjectTypes(value) {
1613
+ return splitListValue(value).filter((entry) => entry === "star" || entry === "planet" || entry === "moon" || entry === "belt" || entry === "asteroid" || entry === "comet" || entry === "ring" || entry === "structure" || entry === "phenomenon");
1614
+ }
1615
+ function parseViewpointGroups(value, relationships, objectMap) {
1616
+ return splitListValue(value).map((entry) => {
1617
+ if (entry.startsWith("wo-") && entry.endsWith("-group")) {
1618
+ return entry;
1619
+ }
1620
+ if (relationships.groupIds.has(entry)) {
1621
+ return relationships.groupIds.get(entry) ?? createGroupId(entry);
1622
+ }
1623
+ return objectMap.has(entry) ? createGroupId(entry) : createGroupId(entry);
1624
+ });
1625
+ }
1626
+ function splitListValue(value) {
1627
+ return value.split(/[\s,]+/).map((entry) => entry.trim()).filter(Boolean);
1628
+ }
1629
+ function normalizeViewpointId(value) {
1630
+ return value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
1631
+ }
1632
+ function humanizeIdentifier(value) {
1633
+ return value.split(/[-_]+/).filter(Boolean).map((segment) => segment[0].toUpperCase() + segment.slice(1)).join(" ");
1634
+ }
1635
+ function createViewpointSummary(label, objectId, filter) {
1636
+ const parts = [label];
1637
+ if (objectId) {
1638
+ parts.push(`focus ${objectId}`);
1639
+ }
1640
+ if (filter?.objectTypes.length) {
1641
+ parts.push(filter.objectTypes.join("/"));
1642
+ }
1643
+ if (filter?.tags.length) {
1644
+ parts.push(`tags ${filter.tags.join(", ")}`);
1645
+ }
1646
+ if (filter?.query) {
1647
+ parts.push(`query "${filter.query}"`);
1648
+ }
1649
+ return parts.join(" - ");
1650
+ }
1651
+ function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels) {
1652
+ let minX = Number.POSITIVE_INFINITY;
1653
+ let minY = Number.POSITIVE_INFINITY;
1654
+ let maxX = Number.NEGATIVE_INFINITY;
1655
+ let maxY = Number.NEGATIVE_INFINITY;
1656
+ const include = (x, y) => {
1657
+ minX = Math.min(minX, x);
1658
+ minY = Math.min(minY, y);
1659
+ maxX = Math.max(maxX, x);
1660
+ maxY = Math.max(maxY, y);
1661
+ };
1662
+ for (const orbit of orbitVisuals) {
1663
+ if (orbit.hidden)
1664
+ continue;
1665
+ includeOrbitBounds(orbit, include);
1666
+ }
1667
+ for (const leader of leaders) {
1668
+ if (leader.hidden)
1669
+ continue;
1670
+ include(leader.x1, leader.y1);
1671
+ include(leader.x2, leader.y2);
1672
+ }
1673
+ for (const object of objects) {
1674
+ if (object.hidden)
1675
+ continue;
1676
+ includeObjectBounds(object, include);
1677
+ }
1678
+ for (const label of labels) {
1679
+ if (label.hidden)
1680
+ continue;
1681
+ includeLabelBounds(label, include);
1682
+ }
1683
+ if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
1684
+ return createBounds(0, 0, width, height);
1685
+ }
1686
+ return createBounds(minX, minY, maxX, maxY);
1687
+ }
1688
+ function includeOrbitBounds(orbit, include) {
1689
+ const strokePadding = orbit.bandThickness !== void 0 ? orbit.bandThickness / 2 + 4 : orbit.band ? 10 : 3;
1690
+ if (orbit.kind === "circle" && orbit.radius !== void 0) {
1691
+ include(orbit.cx - orbit.radius - strokePadding, orbit.cy - orbit.radius - strokePadding);
1692
+ include(orbit.cx + orbit.radius + strokePadding, orbit.cy + orbit.radius + strokePadding);
1693
+ return;
1694
+ }
1695
+ const rx = orbit.rx ?? orbit.radius ?? 0;
1696
+ const ry = orbit.ry ?? orbit.radius ?? 0;
1697
+ const points = sampleEllipseArcPoints(orbit.cx, orbit.cy, rx, ry, orbit.rotationDeg, 0, Math.PI * 2, ARC_SAMPLE_COUNT * 2);
1698
+ for (const point of points) {
1699
+ include(point.x - strokePadding, point.y - strokePadding);
1700
+ include(point.x + strokePadding, point.y + strokePadding);
1701
+ }
1702
+ }
1703
+ function createBounds(minX, minY, maxX, maxY) {
1704
+ return {
1705
+ minX,
1706
+ minY,
1707
+ maxX,
1708
+ maxY,
1709
+ width: maxX - minX,
1710
+ height: maxY - minY,
1711
+ centerX: minX + (maxX - minX) / 2,
1712
+ centerY: minY + (maxY - minY) / 2
1713
+ };
1714
+ }
1715
+ function includeObjectBounds(object, include) {
1716
+ include(object.x - object.visualRadius - 24, object.y - object.visualRadius - 16);
1717
+ include(object.x + object.visualRadius + 24, object.y + object.visualRadius + 36);
1718
+ }
1719
+ function includeLabelBounds(label, include) {
1720
+ const labelScale = 1;
1721
+ const labelHalfWidth = estimateLabelHalfWidthFromText(label.label, label.secondaryLabel, labelScale);
1722
+ include(label.x - labelHalfWidth, label.y - 18);
1723
+ include(label.x + labelHalfWidth, label.y + 8);
1724
+ include(label.x - labelHalfWidth, label.secondaryY - 14);
1725
+ include(label.x + labelHalfWidth, label.secondaryY + 8);
1726
+ }
1727
+ function placeObject(object, x, y, depth, positions, orbitDrafts, leaderDrafts, context) {
1728
+ if (positions.has(object.id)) {
1729
+ return;
1730
+ }
1731
+ positions.set(object.id, {
1732
+ object,
1733
+ x,
1734
+ y,
1735
+ radius: visualRadiusFor(object, depth, context.scaleModel),
1736
+ sortKey: computeSortKey(x, y, depth)
1737
+ });
1738
+ placeOrbitingChildren(object, positions, orbitDrafts, leaderDrafts, context, depth + 1);
1739
+ }
1740
+ function placeOrbitingChildren(object, positions, orbitDrafts, leaderDrafts, context, depth) {
1741
+ const parent = positions.get(object.id);
1742
+ if (!parent) {
1743
+ return;
1744
+ }
1745
+ const orbiting = [...context.orbitChildren.get(object.id) ?? []].sort(compareOrbiting);
1746
+ const orbitMetricContext = computeOrbitMetricContext(orbiting, parent.radius, context.spacingFactor, context.scaleModel);
1747
+ orbiting.forEach((child, index) => {
1748
+ const orbitGeometry = resolveOrbitGeometry(child, index, orbiting.length, parent, orbitMetricContext, context);
1749
+ orbitDrafts.push({
1750
+ object: child,
1751
+ parentId: object.id,
1752
+ kind: orbitGeometry.kind,
1753
+ cx: orbitGeometry.cx,
1754
+ cy: orbitGeometry.cy,
1755
+ radius: orbitGeometry.radius,
1756
+ rx: orbitGeometry.rx,
1757
+ ry: orbitGeometry.ry,
1758
+ rotationDeg: orbitGeometry.rotationDeg,
1759
+ band: orbitGeometry.band,
1760
+ bandThickness: orbitGeometry.bandThickness,
1761
+ frontArcPath: orbitGeometry.frontArcPath,
1762
+ backArcPath: orbitGeometry.backArcPath
1763
+ });
1764
+ placeObject(child, orbitGeometry.objectX, orbitGeometry.objectY, depth, positions, orbitDrafts, leaderDrafts, context);
1765
+ });
1766
+ const surfaceObjects = [...context.surfaceChildren.get(object.id) ?? []];
1767
+ surfaceObjects.forEach((child, index) => {
1768
+ const angle = angleForIndex(index, surfaceObjects.length, -Math.PI / 3);
1769
+ const leaderDistance = 28 * context.spacingFactor;
1770
+ const anchorOffset = projectPolarOffset(angle, parent.radius, context.projection, context.projection === "isometric" ? 0.9 : 1);
1771
+ const bodyOffset = projectPolarOffset(angle, parent.radius + leaderDistance, context.projection, context.projection === "isometric" ? 0.9 : 1);
1772
+ const anchorX = parent.x + anchorOffset.x;
1773
+ const anchorY = parent.y + anchorOffset.y;
1774
+ const x = parent.x + bodyOffset.x;
1775
+ const y = parent.y + bodyOffset.y;
1776
+ positions.set(child.id, {
1777
+ object: child,
1778
+ x,
1779
+ y,
1780
+ radius: visualRadiusFor(child, depth + 1, context.scaleModel),
1781
+ sortKey: computeSortKey(x, y, depth + 1),
1782
+ anchorX,
1783
+ anchorY
1784
+ });
1785
+ leaderDrafts.push({
1786
+ object: child,
1787
+ groupId: context.objectMap.has(child.id) ? createGroupId(resolveGroupRootObjectId(child, context.objectMap)) : null,
1788
+ x1: anchorX,
1789
+ y1: anchorY,
1790
+ x2: x,
1791
+ y2: y,
1792
+ mode: "surface"
1793
+ });
1794
+ placeOrbitingChildren(child, positions, orbitDrafts, leaderDrafts, context, depth + 1);
1795
+ });
1796
+ }
1797
+ function compareOrbiting(left, right) {
1798
+ const leftMetric = orbitMetric(left);
1799
+ const rightMetric = orbitMetric(right);
1800
+ if (leftMetric !== null && rightMetric !== null && leftMetric !== rightMetric) {
1801
+ return leftMetric - rightMetric;
1802
+ }
1803
+ if (leftMetric !== null && rightMetric === null)
1804
+ return -1;
1805
+ if (leftMetric === null && rightMetric !== null)
1806
+ return 1;
1807
+ return left.id.localeCompare(right.id);
1808
+ }
1809
+ function computeOrbitMetricContext(objects, parentRadius, spacingFactor, scaleModel) {
1810
+ const metrics = objects.map((object) => orbitMetric(object));
1811
+ const presentMetrics = metrics.filter((value) => value !== null);
1812
+ const innerPx = parentRadius + 56 * spacingFactor * scaleModel.orbitDistanceMultiplier;
1813
+ const stepPx = (objects.length > 2 ? 54 : 64) * spacingFactor * scaleModel.orbitDistanceMultiplier;
1814
+ if (presentMetrics.length === 0) {
1815
+ return {
1816
+ metrics,
1817
+ minMetric: 0,
1818
+ maxMetric: 0,
1819
+ metricSpread: 0,
1820
+ innerPx,
1821
+ stepPx,
1822
+ pixelSpread: Math.max(stepPx * Math.max(objects.length - 1, 1), stepPx)
1823
+ };
1824
+ }
1825
+ const minMetric = Math.min(...presentMetrics);
1826
+ const maxMetric = Math.max(...presentMetrics);
1827
+ const metricSpread = maxMetric - minMetric;
1828
+ return {
1829
+ metrics,
1830
+ minMetric,
1831
+ maxMetric,
1832
+ metricSpread,
1833
+ innerPx,
1834
+ stepPx,
1835
+ pixelSpread: Math.max(stepPx * Math.max(objects.length - 1, 1), stepPx)
1836
+ };
1837
+ }
1838
+ function resolveOrbitGeometry(object, index, count, parent, metricContext, context) {
1839
+ const placement = object.placement;
1840
+ const band = object.type === "belt" || object.type === "ring";
1841
+ if (!placement || placement.mode !== "orbit") {
1842
+ const fallbackRadius = metricContext.innerPx + index * metricContext.stepPx;
1843
+ return {
1844
+ kind: "circle",
1845
+ cx: parent.x,
1846
+ cy: parent.y,
1847
+ radius: fallbackRadius,
1848
+ rotationDeg: 0,
1849
+ band,
1850
+ bandThickness: band ? 12 * context.scaleModel.ringThicknessMultiplier : void 0,
1851
+ objectX: parent.x,
1852
+ objectY: parent.y - fallbackRadius
1853
+ };
1854
+ }
1855
+ const eccentricity = clampNumber(typeof placement.eccentricity === "number" ? placement.eccentricity : 0, 0, 0.92);
1856
+ const semiMajor = resolveOrbitRadiusPx(object, index, metricContext);
1857
+ const baseMinor = Math.max(semiMajor * Math.sqrt(1 - eccentricity * eccentricity), semiMajor * 0.18);
1858
+ const inclinationDeg = unitValueToDegrees(placement.inclination) ?? 0;
1859
+ const inclinationScale = context.projection === "isometric" ? Math.max(MIN_ISO_MINOR_SCALE, Math.cos(degreesToRadians(inclinationDeg))) * ISO_FLATTENING : 1;
1860
+ const semiMinor = Math.max(baseMinor * inclinationScale, semiMajor * 0.14);
1861
+ const rotationDeg = unitValueToDegrees(placement.angle) ?? 0;
1862
+ const focusOffset = semiMajor * eccentricity;
1863
+ const centerOffset = rotateOffset(-focusOffset, 0, rotationDeg);
1864
+ const cx = parent.x + centerOffset.x;
1865
+ const cy = parent.y + centerOffset.y;
1866
+ const phase = resolveOrbitPhase(placement.phase, index, count);
1867
+ const objectPoint = ellipsePoint(cx, cy, semiMajor, semiMinor, rotationDeg, phase);
1868
+ const useCircle = context.projection === "topdown" && eccentricity <= 1e-4 && Math.abs(rotationDeg) <= 1e-4;
1869
+ const bandThickness = band ? resolveBandThickness(object, semiMajor, metricContext, context.scaleModel) : void 0;
1870
+ return {
1871
+ kind: useCircle ? "circle" : "ellipse",
1872
+ cx: useCircle ? parent.x : cx,
1873
+ cy: useCircle ? parent.y : cy,
1874
+ radius: useCircle ? semiMajor : void 0,
1875
+ rx: useCircle ? void 0 : semiMajor,
1876
+ ry: useCircle ? void 0 : semiMinor,
1877
+ rotationDeg,
1878
+ band,
1879
+ bandThickness,
1880
+ frontArcPath: context.projection === "isometric" || band ? buildEllipseArcPath(cx, cy, semiMajor, semiMinor, rotationDeg, 0, Math.PI) : void 0,
1881
+ backArcPath: context.projection === "isometric" || band ? buildEllipseArcPath(cx, cy, semiMajor, semiMinor, rotationDeg, Math.PI, Math.PI * 2) : void 0,
1882
+ objectX: objectPoint.x,
1883
+ objectY: objectPoint.y
1884
+ };
1885
+ }
1886
+ function resolveOrbitRadiusPx(object, index, metricContext) {
1887
+ const metric = orbitMetric(object);
1888
+ if (metric === null) {
1889
+ return metricContext.innerPx + index * metricContext.stepPx;
1890
+ }
1891
+ if (metricContext.metricSpread > 0) {
1892
+ return metricContext.innerPx + (metric - metricContext.minMetric) / metricContext.metricSpread * metricContext.pixelSpread;
1893
+ }
1894
+ return metricContext.innerPx + Math.log10(metric + 1) * metricContext.stepPx;
1895
+ }
1896
+ function orbitMetric(object) {
1897
+ if (!object.placement || object.placement.mode !== "orbit") {
1898
+ return null;
1899
+ }
1900
+ return toDistanceMetric(object.placement.semiMajor ?? object.placement.distance ?? null);
1901
+ }
1902
+ function resolveOrbitPhase(phase, index, count) {
1903
+ const degreeValue = phase ? unitValueToDegrees(phase) : null;
1904
+ if (degreeValue !== null) {
1905
+ return degreesToRadians(degreeValue - 90);
1906
+ }
1907
+ return angleForIndex(index, count, -Math.PI / 2);
1908
+ }
1909
+ function resolveBandThickness(object, orbitRadius, metricContext, scaleModel) {
1910
+ const innerMetric = toDistanceMetric(toUnitValue(object.properties.inner));
1911
+ const outerMetric = toDistanceMetric(toUnitValue(object.properties.outer));
1912
+ if (innerMetric !== null && outerMetric !== null) {
1913
+ const thicknessMetric = Math.abs(outerMetric - innerMetric);
1914
+ if (metricContext.metricSpread > 0) {
1915
+ return clampNumber(thicknessMetric / metricContext.metricSpread * metricContext.pixelSpread * scaleModel.ringThicknessMultiplier, 8, 54);
1916
+ }
1917
+ const referenceMetric = Math.max(Math.max(innerMetric, outerMetric), 1e-4);
1918
+ return clampNumber(thicknessMetric / referenceMetric * orbitRadius * 0.75 * scaleModel.ringThicknessMultiplier, 8, 48);
1919
+ }
1920
+ const fallbackBase = object.type === "belt" ? 18 : 12;
1921
+ return fallbackBase * scaleModel.ringThicknessMultiplier;
1922
+ }
1923
+ function resolveAtPosition(reference, positions, objectMap, index, count, width, height, padding, context) {
1924
+ if (reference.kind === "lagrange") {
1925
+ return resolveLagrangePosition(reference, positions, objectMap, width, height);
1926
+ }
1927
+ if (reference.kind === "anchor") {
1928
+ const anchor = positions.get(reference.objectId);
1929
+ if (anchor) {
1930
+ const angle = angleForIndex(index, count, Math.PI / 5);
1931
+ const distance = (anchor.radius + 36) * context.scaleModel.labelMultiplier;
1932
+ const offset = projectPolarOffset(angle, distance, context.projection, context.projection === "isometric" ? 0.92 : 1);
1933
+ return {
1934
+ x: anchor.x + offset.x,
1935
+ y: anchor.y + offset.y,
1936
+ anchorX: anchor.x,
1937
+ anchorY: anchor.y
1938
+ };
1939
+ }
1940
+ }
1941
+ if (reference.kind === "named") {
1942
+ const anchor = positions.get(reference.name);
1943
+ if (anchor) {
1944
+ const angle = angleForIndex(index, count, Math.PI / 6);
1945
+ const distance = (anchor.radius + 36) * context.scaleModel.labelMultiplier;
1946
+ const offset = projectPolarOffset(angle, distance, context.projection, context.projection === "isometric" ? 0.92 : 1);
1947
+ return {
1948
+ x: anchor.x + offset.x,
1949
+ y: anchor.y + offset.y,
1950
+ anchorX: anchor.x,
1951
+ anchorY: anchor.y
1952
+ };
1953
+ }
1954
+ }
1955
+ return {
1956
+ x: width - padding - 170,
1957
+ y: height - padding - 86 - index * 58 * context.scaleModel.freePlacementMultiplier
1958
+ };
1959
+ }
1960
+ function resolveLagrangePosition(reference, positions, objectMap, width, height) {
1961
+ const primary = reference.secondary ? positions.get(reference.primary) : deriveParentAnchor(reference.primary, positions, objectMap);
1962
+ const secondary = positions.get(reference.secondary ?? reference.primary);
1963
+ if (!primary || !secondary) {
1964
+ return {
1965
+ x: width * 0.7,
1966
+ y: height * 0.25
1967
+ };
1968
+ }
1969
+ const dx = secondary.x - primary.x;
1970
+ const dy = secondary.y - primary.y;
1971
+ const distance = Math.hypot(dx, dy) || 1;
1972
+ const ux = dx / distance;
1973
+ const uy = dy / distance;
1974
+ const nx = -uy;
1975
+ const ny = ux;
1976
+ const offset = clampNumber(distance * 0.25, 24, 68);
1977
+ switch (reference.point) {
1978
+ case "L1":
1979
+ return {
1980
+ x: secondary.x - ux * offset,
1981
+ y: secondary.y - uy * offset,
1982
+ anchorX: secondary.x,
1983
+ anchorY: secondary.y
1984
+ };
1985
+ case "L2":
1986
+ return {
1987
+ x: secondary.x + ux * offset,
1988
+ y: secondary.y + uy * offset,
1989
+ anchorX: secondary.x,
1990
+ anchorY: secondary.y
1991
+ };
1992
+ case "L3":
1993
+ return {
1994
+ x: primary.x - ux * offset,
1995
+ y: primary.y - uy * offset,
1996
+ anchorX: primary.x,
1997
+ anchorY: primary.y
1998
+ };
1999
+ case "L4":
2000
+ return {
2001
+ x: secondary.x + (ux * 0.5 - nx * 0.8660254) * offset,
2002
+ y: secondary.y + (uy * 0.5 - ny * 0.8660254) * offset,
2003
+ anchorX: secondary.x,
2004
+ anchorY: secondary.y
2005
+ };
2006
+ case "L5":
2007
+ return {
2008
+ x: secondary.x + (ux * 0.5 + nx * 0.8660254) * offset,
2009
+ y: secondary.y + (uy * 0.5 + ny * 0.8660254) * offset,
2010
+ anchorX: secondary.x,
2011
+ anchorY: secondary.y
2012
+ };
2013
+ }
2014
+ }
2015
+ function buildSceneRelationships(objects, objectMap) {
2016
+ const parentIds = /* @__PURE__ */ new Map();
2017
+ const childIds = /* @__PURE__ */ new Map();
2018
+ for (const object of objects) {
2019
+ const parentId = resolveParentId(object, objectMap);
2020
+ parentIds.set(object.id, parentId);
2021
+ if (parentId) {
2022
+ const existing = childIds.get(parentId);
2023
+ if (existing) {
2024
+ existing.push(object.id);
2025
+ } else {
2026
+ childIds.set(parentId, [object.id]);
2027
+ }
2028
+ }
2029
+ if (!childIds.has(object.id)) {
2030
+ childIds.set(object.id, []);
2031
+ }
2032
+ }
2033
+ const ancestorIds = /* @__PURE__ */ new Map();
2034
+ const groupIds = /* @__PURE__ */ new Map();
2035
+ const groupRoots = /* @__PURE__ */ new Map();
2036
+ const buildAncestors = (objectId) => {
2037
+ const cached = ancestorIds.get(objectId);
2038
+ if (cached) {
2039
+ return cached;
2040
+ }
2041
+ const seen = /* @__PURE__ */ new Set();
2042
+ const results = [];
2043
+ let cursor = parentIds.get(objectId) ?? null;
2044
+ while (cursor && !seen.has(cursor)) {
2045
+ results.push(cursor);
2046
+ seen.add(cursor);
2047
+ cursor = parentIds.get(cursor) ?? null;
2048
+ }
2049
+ ancestorIds.set(objectId, results);
2050
+ return results;
2051
+ };
2052
+ const resolveGroupRootObjectId2 = (objectId) => {
2053
+ const cached = groupRoots.get(groupIds.get(objectId) ?? "");
2054
+ if (cached) {
2055
+ return cached;
2056
+ }
2057
+ const parentId = parentIds.get(objectId) ?? null;
2058
+ const object = objectMap.get(objectId);
2059
+ let rootObjectId = objectId;
2060
+ if (object?.placement && object.placement.mode !== "free" && parentId) {
2061
+ rootObjectId = resolveGroupRootObjectId2(parentId);
2062
+ }
2063
+ return rootObjectId;
2064
+ };
2065
+ for (const object of objects) {
2066
+ buildAncestors(object.id);
2067
+ const rootObjectId = resolveGroupRootObjectId2(object.id);
2068
+ const groupId = createGroupId(rootObjectId);
2069
+ groupIds.set(object.id, groupId);
2070
+ groupRoots.set(groupId, rootObjectId);
2071
+ }
2072
+ return {
2073
+ parentIds,
2074
+ childIds,
2075
+ ancestorIds,
2076
+ groupIds,
2077
+ groupRoots
2078
+ };
2079
+ }
2080
+ function resolveParentId(object, objectMap) {
2081
+ const placement = object.placement;
2082
+ if (!placement) {
2083
+ return null;
2084
+ }
2085
+ switch (placement.mode) {
2086
+ case "orbit":
2087
+ case "surface":
2088
+ return objectMap.has(placement.target) ? placement.target : null;
2089
+ case "at":
2090
+ switch (placement.reference.kind) {
2091
+ case "anchor":
2092
+ return objectMap.has(placement.reference.objectId) ? placement.reference.objectId : null;
2093
+ case "named":
2094
+ return objectMap.has(placement.reference.name) ? placement.reference.name : null;
2095
+ case "lagrange":
2096
+ if (placement.reference.secondary && objectMap.has(placement.reference.secondary)) {
2097
+ return placement.reference.secondary;
2098
+ }
2099
+ return objectMap.has(placement.reference.primary) ? placement.reference.primary : null;
2100
+ }
2101
+ case "free":
2102
+ return null;
2103
+ }
2104
+ }
2105
+ function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels) {
2106
+ let minX = Number.POSITIVE_INFINITY;
2107
+ let minY = Number.POSITIVE_INFINITY;
2108
+ let maxX = Number.NEGATIVE_INFINITY;
2109
+ let maxY = Number.NEGATIVE_INFINITY;
2110
+ const include = (x, y) => {
2111
+ minX = Math.min(minX, x);
2112
+ minY = Math.min(minY, y);
2113
+ maxX = Math.max(maxX, x);
2114
+ maxY = Math.max(maxY, y);
2115
+ };
2116
+ for (const object of objects) {
2117
+ if (!object.hidden && group.objectIds.includes(object.objectId)) {
2118
+ includeObjectBounds(object, include);
2119
+ }
2120
+ }
2121
+ for (const orbit of orbitVisuals) {
2122
+ if (!orbit.hidden && group.orbitIds.includes(orbit.objectId)) {
2123
+ includeOrbitBounds(orbit, include);
2124
+ }
2125
+ }
2126
+ for (const leader of leaders) {
2127
+ if (!leader.hidden && group.leaderIds.includes(leader.objectId)) {
2128
+ include(leader.x1, leader.y1);
2129
+ include(leader.x2, leader.y2);
2130
+ }
2131
+ }
2132
+ for (const label of labels) {
2133
+ if (!label.hidden && group.labelIds.includes(label.objectId)) {
2134
+ includeLabelBounds(label, include);
2135
+ }
2136
+ }
2137
+ if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
2138
+ return createBounds(0, 0, 0, 0);
2139
+ }
2140
+ return createBounds(minX, minY, maxX, maxY);
2141
+ }
2142
+ function resolveGroupRootObjectId(object, objectMap) {
2143
+ let current = object;
2144
+ const seen = /* @__PURE__ */ new Set();
2145
+ while (current.placement && current.placement.mode !== "free" && !seen.has(current.id)) {
2146
+ seen.add(current.id);
2147
+ const parentId = resolveParentId(current, objectMap);
2148
+ if (!parentId) {
2149
+ break;
2150
+ }
2151
+ const parent = objectMap.get(parentId);
2152
+ if (!parent) {
2153
+ break;
2154
+ }
2155
+ current = parent;
2156
+ }
2157
+ return current.id;
2158
+ }
2159
+ function createLabelRect(x, labelY, secondaryY, labelHalfWidth, direction) {
2160
+ return {
2161
+ left: x - labelHalfWidth,
2162
+ right: x + labelHalfWidth,
2163
+ top: Math.min(labelY, secondaryY) - (direction < 0 ? 18 : 12),
2164
+ bottom: Math.max(labelY, secondaryY) + (direction < 0 ? 8 : 12)
2165
+ };
2166
+ }
2167
+ function rectsOverlap(left, right) {
2168
+ return !(left.right < right.left || right.right < left.left || left.bottom < right.top || right.bottom < left.top);
2169
+ }
2170
+ function deriveParentAnchor(objectId, positions, objectMap) {
2171
+ const object = objectMap.get(objectId);
2172
+ if (!object?.placement || object.placement.mode !== "orbit") {
2173
+ return positions.get(objectId);
2174
+ }
2175
+ return positions.get(object.placement.target);
2176
+ }
2177
+ function visualRadiusFor(object, depth, scaleModel) {
2178
+ const explicitRadius = toVisualSizeMetric(object.properties.radius, scaleModel);
2179
+ if (explicitRadius !== null) {
2180
+ return explicitRadius;
2181
+ }
2182
+ const multiplier = scaleModel.bodyRadiusMultiplier;
2183
+ switch (object.type) {
2184
+ case "star":
2185
+ return clampNumber((depth === 0 ? 28 : 20) * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
2186
+ case "planet":
2187
+ return clampNumber(12 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
2188
+ case "moon":
2189
+ return clampNumber(7 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
2190
+ case "belt":
2191
+ return clampNumber(5 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
2192
+ case "asteroid":
2193
+ return clampNumber(5 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
2194
+ case "comet":
2195
+ return clampNumber(6 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
2196
+ case "ring":
2197
+ return clampNumber(5 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
2198
+ case "structure":
2199
+ return clampNumber(6 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
2200
+ case "phenomenon":
2201
+ return clampNumber(8 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
2202
+ }
2203
+ }
2204
+ function visualExtentForObject(object, radius, scaleModel) {
2205
+ const atmosphereBoost = typeof object.properties.atmosphere === "string" ? 4 : 0;
2206
+ switch (object.type) {
2207
+ case "star":
2208
+ return radius * 2.4;
2209
+ case "phenomenon":
2210
+ return radius * 1.25;
2211
+ case "structure":
2212
+ return radius + 2;
2213
+ default:
2214
+ return Math.min(radius + atmosphereBoost, scaleModel.maxBodyRadius + 10);
2215
+ }
2216
+ }
2217
+ function toDistanceMetric(value) {
2218
+ if (!value)
2219
+ return null;
2220
+ switch (value.unit) {
2221
+ case "au":
2222
+ return value.value;
2223
+ case "km":
2224
+ return value.value / AU_IN_KM;
2225
+ case "re":
2226
+ return value.value * EARTH_RADIUS_IN_KM / AU_IN_KM;
2227
+ case "sol":
2228
+ return value.value * SOLAR_RADIUS_IN_KM / AU_IN_KM;
2229
+ default:
2230
+ return value.value;
2231
+ }
2232
+ }
2233
+ function freePlacementOffsetPx(distance, scaleModel) {
2234
+ const metric = toDistanceMetric(distance ?? null);
2235
+ if (metric === null || metric <= 0) {
2236
+ return 0;
2237
+ }
2238
+ return clampNumber(metric * 96 * scaleModel.freePlacementMultiplier, 0, 420);
2239
+ }
2240
+ function toVisualSizeMetric(value, scaleModel) {
2241
+ const unitValue = toUnitValue(value);
2242
+ if (!unitValue) {
2243
+ return null;
2244
+ }
2245
+ let size;
2246
+ switch (unitValue.unit) {
2247
+ case "sol":
2248
+ size = clampNumber(unitValue.value * 22, 14, 40);
2249
+ break;
2250
+ case "re":
2251
+ size = clampNumber(unitValue.value * 10, 6, 18);
2252
+ break;
2253
+ case "km":
2254
+ size = clampNumber(Math.log10(Math.max(unitValue.value, 1)) * 2.6, 4, 16);
2255
+ break;
2256
+ default:
2257
+ size = clampNumber(unitValue.value * 4, 4, 20);
2258
+ break;
2259
+ }
2260
+ return clampNumber(size * scaleModel.bodyRadiusMultiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
2261
+ }
2262
+ function toUnitValue(value) {
2263
+ if (!value || typeof value !== "object" || !("value" in value)) {
2264
+ return null;
2265
+ }
2266
+ return value;
2267
+ }
2268
+ function unitValueToDegrees(value) {
2269
+ if (!value) {
2270
+ return null;
2271
+ }
2272
+ return value.unit === "deg" || value.unit === null ? value.value : null;
2273
+ }
2274
+ function angleForIndex(index, count, startAngle) {
2275
+ if (count <= 1)
2276
+ return startAngle;
2277
+ return startAngle + index * Math.PI * 2 / count;
2278
+ }
2279
+ function buildEllipseArcPath(cx, cy, rx, ry, rotationDeg, start, end) {
2280
+ const points = sampleEllipseArcPoints(cx, cy, rx, ry, rotationDeg, start, end, ARC_SAMPLE_COUNT);
2281
+ if (points.length === 0) {
2282
+ return "";
2283
+ }
2284
+ return points.map((point, index) => `${index === 0 ? "M" : "L"} ${formatNumber(point.x)} ${formatNumber(point.y)}`).join(" ");
2285
+ }
2286
+ function sampleEllipseArcPoints(cx, cy, rx, ry, rotationDeg, start, end, segments) {
2287
+ const points = [];
2288
+ for (let index = 0; index <= segments; index += 1) {
2289
+ const t = start + (end - start) * index / segments;
2290
+ points.push(ellipsePoint(cx, cy, rx, ry, rotationDeg, t));
2291
+ }
2292
+ return points;
2293
+ }
2294
+ function ellipsePoint(cx, cy, rx, ry, rotationDeg, angle) {
2295
+ const localX = rx * Math.cos(angle);
2296
+ const localY = ry * Math.sin(angle);
2297
+ const rotated = rotateOffset(localX, localY, rotationDeg);
2298
+ return {
2299
+ x: cx + rotated.x,
2300
+ y: cy + rotated.y
2301
+ };
2302
+ }
2303
+ function rotateOffset(x, y, rotationDeg) {
2304
+ const radians = degreesToRadians(rotationDeg);
2305
+ return {
2306
+ x: x * Math.cos(radians) - y * Math.sin(radians),
2307
+ y: x * Math.sin(radians) + y * Math.cos(radians)
2308
+ };
2309
+ }
2310
+ function projectPolarOffset(angle, distance, projection, verticalFactor) {
2311
+ const yScale = projection === "isometric" ? ISO_FLATTENING * verticalFactor : verticalFactor;
2312
+ return {
2313
+ x: Math.cos(angle) * distance,
2314
+ y: Math.sin(angle) * distance * yScale
2315
+ };
2316
+ }
2317
+ function computeSortKey(x, y, depth) {
2318
+ return y * 1e3 + x + depth * 0.01;
2319
+ }
2320
+ function clampNumber(value, min, max) {
2321
+ return Math.min(Math.max(value, min), max);
2322
+ }
2323
+ function pushGrouped(map, key, value) {
2324
+ const existing = map.get(key);
2325
+ if (existing) {
2326
+ existing.push(value);
2327
+ } else {
2328
+ map.set(key, [value]);
2329
+ }
2330
+ }
2331
+ function createRenderId(objectId) {
2332
+ const normalized = objectId.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "object";
2333
+ return `wo-${normalized}`;
2334
+ }
2335
+ function createGroupId(objectId) {
2336
+ return `${createRenderId(objectId)}-group`;
2337
+ }
2338
+ function customColorFor(value) {
2339
+ return typeof value === "string" && value.trim() ? value : void 0;
2340
+ }
2341
+ function estimateLabelHalfWidth(object, labelMultiplier) {
2342
+ const primaryWidth = object.label.length * 4.6 * labelMultiplier + 18;
2343
+ const secondaryWidth = object.secondaryLabel.length * 3.9 * labelMultiplier + 18;
2344
+ return Math.max(primaryWidth, secondaryWidth, object.visualRadius + 18);
2345
+ }
2346
+ function estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier) {
2347
+ const primaryWidth = label.length * 4.6 * labelMultiplier + 18;
2348
+ const secondaryWidth = secondaryLabel.length * 3.9 * labelMultiplier + 18;
2349
+ return Math.max(primaryWidth, secondaryWidth, 24);
2350
+ }
2351
+ function capitalizeLabel(value) {
2352
+ return value.length > 0 ? value[0].toUpperCase() + value.slice(1) : value;
2353
+ }
2354
+ function degreesToRadians(value) {
2355
+ return value * Math.PI / 180;
2356
+ }
2357
+ function formatNumber(value) {
2358
+ return Number.isInteger(value) ? String(value) : value.toFixed(2);
2359
+ }
2360
+
2361
+ // packages/core/dist/draft.js
2362
+ function upgradeDocumentToV2(document, options = {}) {
2363
+ const scene = renderDocumentToScene(document, options);
2364
+ const diagnostics = [];
2365
+ const atlasMetadata = collectAtlasMetadata(document, diagnostics);
2366
+ const annotations = collectDraftAnnotations(document, diagnostics);
2367
+ const defaults = createDraftDefaults(document, scene.renderPreset ?? options.preset ?? null, scene.projection);
2368
+ const system = document.system ? createDraftSystem(document, defaults, atlasMetadata, annotations, diagnostics, scene.renderPreset ?? options.preset ?? null) : null;
2369
+ if (scene.viewpoints.some((viewpoint) => !viewpoint.generated)) {
2370
+ diagnostics.push({
2371
+ code: "upgrade.viewpoints.structured",
2372
+ severity: "info",
2373
+ source: "upgrade",
2374
+ message: `Promoted ${scene.viewpoints.filter((viewpoint) => !viewpoint.generated).length} document-defined viewpoint(s) into the 2.0 atlas section.`
2375
+ });
2376
+ }
2377
+ return {
2378
+ format: "worldorbit",
2379
+ version: "2.0",
2380
+ sourceVersion: document.version,
2381
+ system,
2382
+ objects: document.objects.map(cloneWorldOrbitObject),
2383
+ diagnostics
2384
+ };
2385
+ }
2386
+ function upgradeDocumentToDraftV2(document, options = {}) {
2387
+ return convertAtlasDocumentToLegacyDraft(upgradeDocumentToV2(document, options));
2388
+ }
2389
+ function materializeAtlasDocument(document) {
2390
+ const system = document.system ? {
2391
+ type: "system",
2392
+ id: document.system.id,
2393
+ properties: materializeDraftSystemProperties(document.system),
2394
+ info: materializeDraftSystemInfo(document.system)
2395
+ } : null;
2396
+ return {
2397
+ format: "worldorbit",
2398
+ version: "1.0",
2399
+ system,
2400
+ objects: document.objects.map(cloneWorldOrbitObject)
2401
+ };
2402
+ }
2403
+ function materializeDraftDocument(document) {
2404
+ return materializeAtlasDocument(document);
2405
+ }
2406
+ function createDraftSystem(document, defaults, atlasMetadata, annotations, diagnostics, preset) {
2407
+ const scene = renderDocumentToScene(document, {
2408
+ preset: preset ?? void 0,
2409
+ projection: defaults.view
2410
+ });
2411
+ return {
2412
+ type: "system",
2413
+ id: document.system?.id ?? "WorldOrbit",
2414
+ title: typeof document.system?.properties.title === "string" ? document.system.properties.title : null,
2415
+ defaults,
2416
+ atlasMetadata,
2417
+ viewpoints: scene.viewpoints.map(mapSceneViewpointToDraftViewpoint),
2418
+ annotations
2419
+ };
2420
+ }
2421
+ function createDraftDefaults(document, preset, projection) {
2422
+ return {
2423
+ view: typeof document.system?.properties.view === "string" && document.system.properties.view.toLowerCase() === "topdown" ? "topdown" : projection,
2424
+ scale: typeof document.system?.properties.scale === "string" ? document.system.properties.scale : null,
2425
+ units: typeof document.system?.properties.units === "string" ? document.system.properties.units : null,
2426
+ preset,
2427
+ theme: typeof document.system?.info["atlas.theme"] === "string" ? document.system.info["atlas.theme"] : null
2428
+ };
2429
+ }
2430
+ function collectAtlasMetadata(document, diagnostics) {
2431
+ const metadata = {};
2432
+ for (const [key, value] of Object.entries(document.system?.info ?? {})) {
2433
+ if (key.startsWith("viewpoint.") || key.startsWith("annotation.")) {
2434
+ continue;
2435
+ }
2436
+ metadata[key] = value;
2437
+ }
2438
+ const metadataKeys = Object.keys(metadata);
2439
+ if (metadataKeys.length > 0) {
2440
+ diagnostics.push({
2441
+ code: "upgrade.atlasMetadata.preserved",
2442
+ severity: "warning",
2443
+ source: "upgrade",
2444
+ message: `Preserved ${metadataKeys.length} system info entr${metadataKeys.length === 1 ? "y" : "ies"} as atlas metadata in the 2.0 atlas document.`
2445
+ });
2446
+ }
2447
+ return metadata;
2448
+ }
2449
+ function collectDraftAnnotations(document, diagnostics) {
2450
+ const drafts = /* @__PURE__ */ new Map();
2451
+ for (const [key, value] of Object.entries(document.system?.info ?? {})) {
2452
+ if (!key.startsWith("annotation.")) {
2453
+ continue;
2454
+ }
2455
+ const [, rawId, ...fieldParts] = key.split(".");
2456
+ if (!rawId || fieldParts.length === 0) {
2457
+ continue;
2458
+ }
2459
+ const id = normalizeIdentifier(rawId);
2460
+ if (!id) {
2461
+ continue;
2462
+ }
2463
+ const draft = drafts.get(id) ?? { id };
2464
+ const field = fieldParts.join(".").toLowerCase();
2465
+ switch (field) {
2466
+ case "label":
2467
+ draft.label = value;
2468
+ break;
2469
+ case "target":
2470
+ case "object":
2471
+ draft.targetObjectId = value.trim() || null;
2472
+ break;
2473
+ case "body":
2474
+ case "text":
2475
+ case "description":
2476
+ draft.body = value;
2477
+ break;
2478
+ case "tags":
2479
+ draft.tags = splitList(value);
2480
+ break;
2481
+ }
2482
+ drafts.set(id, draft);
2483
+ }
2484
+ for (const object of document.objects) {
2485
+ const description = object.info.description;
2486
+ if (!description) {
2487
+ continue;
2488
+ }
2489
+ const annotationId = normalizeIdentifier(`${object.id}-notes`);
2490
+ if (drafts.has(annotationId)) {
2491
+ continue;
2492
+ }
2493
+ drafts.set(annotationId, {
2494
+ id: annotationId,
2495
+ label: `${object.id} Notes`,
2496
+ targetObjectId: object.id,
2497
+ body: description,
2498
+ tags: Array.isArray(object.properties.tags) ? object.properties.tags.filter((entry) => typeof entry === "string") : []
2499
+ });
2500
+ diagnostics.push({
2501
+ code: "upgrade.annotation.objectDescription",
2502
+ severity: "info",
2503
+ source: "upgrade",
2504
+ message: `Lifted ${object.id}.info.description into structured atlas annotation "${annotationId}".`,
2505
+ objectId: object.id,
2506
+ field: "description"
2507
+ });
2508
+ }
2509
+ return [...drafts.values()].filter((draft) => draft.body || draft.label).map((draft) => ({
2510
+ id: draft.id,
2511
+ label: draft.label ?? humanizeIdentifier2(draft.id),
2512
+ targetObjectId: draft.targetObjectId ?? null,
2513
+ body: draft.body ?? "",
2514
+ tags: draft.tags ?? [],
2515
+ sourceObjectId: draft.targetObjectId ?? null
2516
+ })).sort((left, right) => left.label.localeCompare(right.label));
2517
+ }
2518
+ function mapSceneViewpointToDraftViewpoint(viewpoint) {
2519
+ return {
2520
+ id: viewpoint.id,
2521
+ label: viewpoint.label,
2522
+ summary: viewpoint.summary,
2523
+ focusObjectId: viewpoint.objectId,
2524
+ selectedObjectId: viewpoint.selectedObjectId,
2525
+ projection: viewpoint.projection,
2526
+ preset: viewpoint.preset,
2527
+ zoom: viewpoint.scale,
2528
+ rotationDeg: viewpoint.rotationDeg,
2529
+ layers: { ...viewpoint.layers },
2530
+ filter: viewpoint.filter ? {
2531
+ query: viewpoint.filter.query,
2532
+ objectTypes: [...viewpoint.filter.objectTypes],
2533
+ tags: [...viewpoint.filter.tags],
2534
+ groupIds: [...viewpoint.filter.groupIds]
2535
+ } : null
2536
+ };
2537
+ }
2538
+ function cloneWorldOrbitObject(object) {
2539
+ return {
2540
+ ...object,
2541
+ properties: cloneProperties(object.properties),
2542
+ placement: object.placement ? structuredClone(object.placement) : null,
2543
+ info: { ...object.info }
2544
+ };
2545
+ }
2546
+ function cloneProperties(properties) {
2547
+ const next = {};
2548
+ for (const [key, value] of Object.entries(properties)) {
2549
+ if (Array.isArray(value)) {
2550
+ next[key] = [...value];
2551
+ continue;
2552
+ }
2553
+ if (value && typeof value === "object" && "value" in value) {
2554
+ next[key] = {
2555
+ value: value.value,
2556
+ unit: value.unit
2557
+ };
2558
+ continue;
2559
+ }
2560
+ next[key] = value;
2561
+ }
2562
+ return next;
2563
+ }
2564
+ function splitList(value) {
2565
+ return value.split(/[\s,]+/).map((entry) => entry.trim()).filter(Boolean);
2566
+ }
2567
+ function normalizeIdentifier(value) {
2568
+ return value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
2569
+ }
2570
+ function humanizeIdentifier2(value) {
2571
+ return value.split(/[-_]+/).filter(Boolean).map((segment) => segment[0].toUpperCase() + segment.slice(1)).join(" ");
2572
+ }
2573
+ function materializeDraftSystemProperties(system) {
2574
+ const properties = {};
2575
+ if (system.title) {
2576
+ properties.title = system.title;
2577
+ }
2578
+ properties.view = system.defaults.view;
2579
+ if (system.defaults.scale) {
2580
+ properties.scale = system.defaults.scale;
2581
+ }
2582
+ if (system.defaults.units) {
2583
+ properties.units = system.defaults.units;
2584
+ }
2585
+ return properties;
2586
+ }
2587
+ function materializeDraftSystemInfo(system) {
2588
+ const info = {
2589
+ ...system.atlasMetadata
2590
+ };
2591
+ if (system.defaults.theme) {
2592
+ info["atlas.theme"] = system.defaults.theme;
2593
+ }
2594
+ for (const viewpoint of system.viewpoints) {
2595
+ const prefix = `viewpoint.${viewpoint.id}`;
2596
+ info[`${prefix}.label`] = viewpoint.label;
2597
+ if (viewpoint.summary) {
2598
+ info[`${prefix}.summary`] = viewpoint.summary;
2599
+ }
2600
+ if (viewpoint.focusObjectId) {
2601
+ info[`${prefix}.focus`] = viewpoint.focusObjectId;
2602
+ }
2603
+ if (viewpoint.selectedObjectId) {
2604
+ info[`${prefix}.select`] = viewpoint.selectedObjectId;
2605
+ }
2606
+ if (viewpoint.projection) {
2607
+ info[`${prefix}.projection`] = viewpoint.projection;
2608
+ }
2609
+ if (viewpoint.preset) {
2610
+ info[`${prefix}.preset`] = viewpoint.preset;
2611
+ }
2612
+ if (viewpoint.zoom !== null) {
2613
+ info[`${prefix}.zoom`] = String(viewpoint.zoom);
2614
+ }
2615
+ if (viewpoint.rotationDeg !== 0) {
2616
+ info[`${prefix}.rotation`] = String(viewpoint.rotationDeg);
2617
+ }
2618
+ const serializedLayers = serializeViewpointLayers(viewpoint.layers);
2619
+ if (serializedLayers) {
2620
+ info[`${prefix}.layers`] = serializedLayers;
2621
+ }
2622
+ if (viewpoint.filter?.query) {
2623
+ info[`${prefix}.query`] = viewpoint.filter.query;
2624
+ }
2625
+ if ((viewpoint.filter?.objectTypes.length ?? 0) > 0) {
2626
+ info[`${prefix}.types`] = viewpoint.filter?.objectTypes.join(" ") ?? "";
2627
+ }
2628
+ if ((viewpoint.filter?.tags.length ?? 0) > 0) {
2629
+ info[`${prefix}.tags`] = viewpoint.filter?.tags.join(" ") ?? "";
2630
+ }
2631
+ if ((viewpoint.filter?.groupIds.length ?? 0) > 0) {
2632
+ info[`${prefix}.groups`] = viewpoint.filter?.groupIds.join(" ") ?? "";
2633
+ }
2634
+ }
2635
+ for (const annotation of system.annotations) {
2636
+ const prefix = `annotation.${annotation.id}`;
2637
+ info[`${prefix}.label`] = annotation.label;
2638
+ if (annotation.targetObjectId) {
2639
+ info[`${prefix}.target`] = annotation.targetObjectId;
2640
+ }
2641
+ info[`${prefix}.body`] = annotation.body;
2642
+ if (annotation.tags.length > 0) {
2643
+ info[`${prefix}.tags`] = annotation.tags.join(" ");
2644
+ }
2645
+ if (annotation.sourceObjectId) {
2646
+ info[`${prefix}.source`] = annotation.sourceObjectId;
2647
+ }
2648
+ }
2649
+ return info;
2650
+ }
2651
+ function serializeViewpointLayers(layers) {
2652
+ const tokens = [];
2653
+ const orbitFront = layers["orbits-front"];
2654
+ const orbitBack = layers["orbits-back"];
2655
+ if (orbitFront !== void 0 || orbitBack !== void 0) {
2656
+ tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
2657
+ }
2658
+ for (const key of ["background", "guides", "objects", "labels", "metadata"]) {
2659
+ if (layers[key] !== void 0) {
2660
+ tokens.push(layers[key] ? key : `-${key}`);
2661
+ }
2662
+ }
2663
+ return tokens.join(" ");
2664
+ }
2665
+ function convertAtlasDocumentToLegacyDraft(document) {
2666
+ return {
2667
+ ...document,
2668
+ version: "2.0-draft"
2669
+ };
2670
+ }
2671
+
2672
+ // packages/core/dist/format.js
2673
+ var CANONICAL_FIELD_ORDER = [
2674
+ "title",
2675
+ "view",
2676
+ "scale",
2677
+ "units",
2678
+ "kind",
2679
+ "class",
2680
+ "tags",
2681
+ "color",
2682
+ "image",
2683
+ "hidden",
2684
+ "orbit",
2685
+ "distance",
2686
+ "semiMajor",
2687
+ "eccentricity",
2688
+ "period",
2689
+ "angle",
2690
+ "inclination",
2691
+ "phase",
2692
+ "at",
2693
+ "surface",
2694
+ "free",
2695
+ "radius",
2696
+ "mass",
2697
+ "density",
2698
+ "gravity",
2699
+ "temperature",
2700
+ "albedo",
2701
+ "atmosphere",
2702
+ "inner",
2703
+ "outer",
2704
+ "on",
2705
+ "source",
2706
+ "cycle"
2707
+ ];
2708
+ function formatDocument(document, options = {}) {
2709
+ const schema = options.schema ?? "auto";
2710
+ const useDraft = schema === "2.0" || schema === "2.0-draft" || document.version === "2.0" || document.version === "2.0-draft";
2711
+ if (useDraft) {
2712
+ if (schema === "2.0-draft") {
2713
+ const legacyDraftDocument = document.version === "2.0-draft" ? document : document.version === "2.0" ? {
2714
+ ...document,
2715
+ version: "2.0-draft"
2716
+ } : upgradeDocumentToDraftV2(document);
2717
+ return formatDraftDocument(legacyDraftDocument);
2718
+ }
2719
+ const atlasDocument = document.version === "2.0" ? document : document.version === "2.0-draft" ? {
2720
+ ...document,
2721
+ version: "2.0"
2722
+ } : upgradeDocumentToV2(document);
2723
+ return formatAtlasDocument(atlasDocument);
2724
+ }
2725
+ const lines = [];
2726
+ const stableDocument = document;
2727
+ if (stableDocument.system) {
2728
+ lines.push(...formatSystem(stableDocument.system));
2729
+ }
2730
+ const sortedObjects = [...stableDocument.objects].sort(compareObjects);
2731
+ for (const object of sortedObjects) {
2732
+ if (lines.length > 0) {
2733
+ lines.push("");
2734
+ }
2735
+ lines.push(...formatObject(object));
2736
+ }
2737
+ return lines.join("\n");
2738
+ }
2739
+ function formatAtlasDocument(document) {
2740
+ const lines = ["schema 2.0", ""];
2741
+ if (document.system) {
2742
+ lines.push(...formatAtlasSystem(document.system));
2743
+ }
2744
+ const sortedObjects = [...document.objects].sort(compareObjects);
2745
+ if (sortedObjects.length > 0 && lines.at(-1) !== "") {
2746
+ lines.push("");
2747
+ }
2748
+ sortedObjects.forEach((object, index) => {
2749
+ if (index > 0) {
2750
+ lines.push("");
2751
+ }
2752
+ lines.push(...formatAtlasObject(object));
2753
+ });
2754
+ return lines.join("\n");
2755
+ }
2756
+ function formatDraftDocument(document) {
2757
+ const legacy = document.version === "2.0-draft" ? document : {
2758
+ ...document,
2759
+ version: "2.0-draft"
2760
+ };
2761
+ const lines = ["schema 2.0-draft", ""];
2762
+ if (legacy.system) {
2763
+ lines.push(...formatAtlasSystem(legacy.system));
2764
+ }
2765
+ const sortedObjects = [...legacy.objects].sort(compareObjects);
2766
+ if (sortedObjects.length > 0 && lines.at(-1) !== "") {
2767
+ lines.push("");
2768
+ }
2769
+ sortedObjects.forEach((object, index) => {
2770
+ if (index > 0) {
2771
+ lines.push("");
2772
+ }
2773
+ lines.push(...formatAtlasObject(object));
2774
+ });
2775
+ return lines.join("\n");
2776
+ }
2777
+ function formatSystem(system) {
2778
+ return formatLines("system", system.id, system.properties, null, system.info);
2779
+ }
2780
+ function formatAtlasSystem(system) {
2781
+ const lines = [`system ${system.id}`];
2782
+ if (system.title) {
2783
+ lines.push(` title ${quoteIfNeeded(system.title)}`);
2784
+ }
2785
+ lines.push("");
2786
+ lines.push("defaults");
2787
+ lines.push(` view ${system.defaults.view}`);
2788
+ if (system.defaults.scale) {
2789
+ lines.push(` scale ${quoteIfNeeded(system.defaults.scale)}`);
2790
+ }
2791
+ if (system.defaults.units) {
2792
+ lines.push(` units ${quoteIfNeeded(system.defaults.units)}`);
2793
+ }
2794
+ if (system.defaults.preset) {
2795
+ lines.push(` preset ${system.defaults.preset}`);
2796
+ }
2797
+ if (system.defaults.theme) {
2798
+ lines.push(` theme ${quoteIfNeeded(system.defaults.theme)}`);
2799
+ }
2800
+ if (Object.keys(system.atlasMetadata).length > 0) {
2801
+ lines.push("");
2802
+ lines.push("atlas");
2803
+ lines.push(" metadata");
2804
+ for (const [key, value] of Object.entries(system.atlasMetadata).sort(([left], [right]) => left.localeCompare(right))) {
2805
+ lines.push(` ${key} ${quoteIfNeeded(value)}`);
2806
+ }
2807
+ }
2808
+ for (const viewpoint of system.viewpoints) {
2809
+ lines.push("");
2810
+ lines.push(...formatAtlasViewpoint(viewpoint));
2811
+ }
2812
+ for (const annotation of system.annotations) {
2813
+ lines.push("");
2814
+ lines.push(...formatAtlasAnnotation(annotation));
2815
+ }
2816
+ return lines;
2817
+ }
2818
+ function formatObject(object) {
2819
+ return formatLines(object.type, object.id, object.properties, object.placement, object.info);
2820
+ }
2821
+ function formatAtlasObject(object) {
2822
+ return formatLines(`object ${object.type}`, object.id, object.properties, object.placement, object.info);
2823
+ }
2824
+ function formatLines(objectType, id, properties, placement, info) {
2825
+ const lines = [`${objectType} ${id}`];
2826
+ const fieldLines = [...formatPlacement(placement), ...formatProperties(properties)];
2827
+ for (const fieldLine of fieldLines) {
2828
+ lines.push(` ${fieldLine}`);
2829
+ }
2830
+ const infoEntries = Object.entries(info).sort(([left], [right]) => left.localeCompare(right));
2831
+ if (infoEntries.length > 0) {
2832
+ if (fieldLines.length > 0) {
2833
+ lines.push("");
2834
+ }
2835
+ lines.push(" info");
2836
+ for (const [key, value] of infoEntries) {
2837
+ lines.push(` ${key} ${quoteIfNeeded(value)}`);
2838
+ }
2839
+ }
2840
+ return lines;
2841
+ }
2842
+ function formatPlacement(placement) {
2843
+ if (!placement)
2844
+ return [];
2845
+ switch (placement.mode) {
2846
+ case "orbit":
2847
+ return [
2848
+ `orbit ${placement.target}`,
2849
+ ...formatOptionalUnit("distance", placement.distance),
2850
+ ...formatOptionalUnit("semiMajor", placement.semiMajor),
2851
+ ...formatOptionalNumber("eccentricity", placement.eccentricity),
2852
+ ...formatOptionalUnit("period", placement.period),
2853
+ ...formatOptionalUnit("angle", placement.angle),
2854
+ ...formatOptionalUnit("inclination", placement.inclination),
2855
+ ...formatOptionalUnit("phase", placement.phase)
2856
+ ];
2857
+ case "at":
2858
+ return [`at ${formatAtReference(placement.reference)}`];
2859
+ case "surface":
2860
+ return [`surface ${placement.target}`];
2861
+ case "free":
2862
+ return [`free ${placement.distance ? formatUnitValue(placement.distance) : placement.descriptor ?? ""}`.trim()];
2863
+ }
2864
+ }
2865
+ function formatProperties(properties) {
2866
+ return Object.keys(properties).sort(compareFieldKeys).map((key) => `${key} ${formatValue(properties[key])}`);
2867
+ }
2868
+ function formatAtlasViewpoint(viewpoint) {
2869
+ const lines = [`viewpoint ${viewpoint.id}`, ` label ${quoteIfNeeded(viewpoint.label)}`];
2870
+ if (viewpoint.focusObjectId) {
2871
+ lines.push(` focus ${viewpoint.focusObjectId}`);
2872
+ }
2873
+ if (viewpoint.selectedObjectId && viewpoint.selectedObjectId !== viewpoint.focusObjectId) {
2874
+ lines.push(` select ${viewpoint.selectedObjectId}`);
2875
+ }
2876
+ if (viewpoint.summary) {
2877
+ lines.push(` summary ${quoteIfNeeded(viewpoint.summary)}`);
2878
+ }
2879
+ if (viewpoint.projection) {
2880
+ lines.push(` projection ${viewpoint.projection}`);
2881
+ }
2882
+ if (viewpoint.preset) {
2883
+ lines.push(` preset ${viewpoint.preset}`);
2884
+ }
2885
+ if (viewpoint.zoom !== null) {
2886
+ lines.push(` zoom ${viewpoint.zoom}`);
2887
+ }
2888
+ if (viewpoint.rotationDeg !== 0) {
2889
+ lines.push(` rotation ${viewpoint.rotationDeg}`);
2890
+ }
2891
+ const layerTokens = formatDraftLayers(viewpoint.layers);
2892
+ if (layerTokens.length > 0) {
2893
+ lines.push(` layers ${layerTokens.join(" ")}`);
2894
+ }
2895
+ if (viewpoint.filter) {
2896
+ lines.push(" filter");
2897
+ if (viewpoint.filter.query) {
2898
+ lines.push(` query ${quoteIfNeeded(viewpoint.filter.query)}`);
2899
+ }
2900
+ if (viewpoint.filter.objectTypes.length > 0) {
2901
+ lines.push(` objectTypes ${viewpoint.filter.objectTypes.join(" ")}`);
2902
+ }
2903
+ if (viewpoint.filter.tags.length > 0) {
2904
+ lines.push(` tags ${viewpoint.filter.tags.map(quoteIfNeeded).join(" ")}`);
2905
+ }
2906
+ if (viewpoint.filter.groupIds.length > 0) {
2907
+ lines.push(` groups ${viewpoint.filter.groupIds.join(" ")}`);
2908
+ }
2909
+ }
2910
+ return lines;
2911
+ }
2912
+ function formatAtlasAnnotation(annotation) {
2913
+ const lines = [`annotation ${annotation.id}`, ` label ${quoteIfNeeded(annotation.label)}`];
2914
+ if (annotation.targetObjectId) {
2915
+ lines.push(` target ${annotation.targetObjectId}`);
2916
+ }
2917
+ lines.push(` body ${quoteIfNeeded(annotation.body)}`);
2918
+ if (annotation.tags.length > 0) {
2919
+ lines.push(` tags ${annotation.tags.map(quoteIfNeeded).join(" ")}`);
2920
+ }
2921
+ return lines;
2922
+ }
2923
+ function formatValue(value) {
2924
+ if (Array.isArray(value)) {
2925
+ return value.map((item) => quoteIfNeeded(item)).join(" ");
2926
+ }
2927
+ if (typeof value === "boolean") {
2928
+ return value ? "true" : "false";
2929
+ }
2930
+ if (typeof value === "number") {
2931
+ return String(value);
2932
+ }
2933
+ if (typeof value === "string") {
2934
+ return quoteIfNeeded(value);
2935
+ }
2936
+ return formatUnitValue(value);
2937
+ }
2938
+ function formatUnitValue(value) {
2939
+ return `${value.value}${value.unit ?? ""}`;
2940
+ }
2941
+ function formatOptionalUnit(key, value) {
2942
+ return value ? [`${key} ${formatUnitValue(value)}`] : [];
2943
+ }
2944
+ function formatOptionalNumber(key, value) {
2945
+ return value === void 0 ? [] : [`${key} ${value}`];
2946
+ }
2947
+ function formatAtReference(reference) {
2948
+ switch (reference.kind) {
2949
+ case "lagrange":
2950
+ return reference.secondary ? `${reference.primary}-${reference.secondary}:${reference.point}` : `${reference.primary}:${reference.point}`;
2951
+ case "anchor":
2952
+ return `${reference.objectId}:${reference.anchor}`;
2953
+ case "named":
2954
+ return reference.name;
2955
+ }
2956
+ }
2957
+ function formatDraftLayers(layers) {
2958
+ const tokens = [];
2959
+ const orbitFront = layers["orbits-front"];
2960
+ const orbitBack = layers["orbits-back"];
2961
+ if (orbitFront !== void 0 || orbitBack !== void 0) {
2962
+ tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
2963
+ }
2964
+ for (const key of ["background", "guides", "objects", "labels", "metadata"]) {
2965
+ if (layers[key] !== void 0) {
2966
+ tokens.push(layers[key] ? key : `-${key}`);
2967
+ }
2968
+ }
2969
+ return tokens;
2970
+ }
2971
+ function compareFieldKeys(left, right) {
2972
+ const leftIndex = CANONICAL_FIELD_ORDER.indexOf(left);
2973
+ const rightIndex = CANONICAL_FIELD_ORDER.indexOf(right);
2974
+ if (leftIndex === -1 && rightIndex === -1)
2975
+ return left.localeCompare(right);
2976
+ if (leftIndex === -1)
2977
+ return 1;
2978
+ if (rightIndex === -1)
2979
+ return -1;
2980
+ return leftIndex - rightIndex;
2981
+ }
2982
+ function compareObjects(left, right) {
2983
+ const leftIndex = objectTypeIndex(left.type);
2984
+ const rightIndex = objectTypeIndex(right.type);
2985
+ if (leftIndex !== rightIndex)
2986
+ return leftIndex - rightIndex;
2987
+ return left.id.localeCompare(right.id);
2988
+ }
2989
+ function objectTypeIndex(objectType) {
2990
+ switch (objectType) {
2991
+ case "star":
2992
+ return 0;
2993
+ case "planet":
2994
+ return 1;
2995
+ case "moon":
2996
+ return 2;
2997
+ case "belt":
2998
+ return 3;
2999
+ case "asteroid":
3000
+ return 4;
3001
+ case "comet":
3002
+ return 5;
3003
+ case "ring":
3004
+ return 6;
3005
+ case "structure":
3006
+ return 7;
3007
+ case "phenomenon":
3008
+ return 8;
3009
+ }
3010
+ }
3011
+ function quoteIfNeeded(value) {
3012
+ if (!/\s/.test(value) && !value.includes('"')) {
3013
+ return value;
3014
+ }
3015
+ return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
3016
+ }
3017
+
3018
+ // packages/core/dist/draft-parse.js
3019
+ function parseWorldOrbitAtlas(source) {
3020
+ return parseAtlasSource(source, "2.0");
3021
+ }
3022
+ function parseWorldOrbitDraft(source) {
3023
+ return parseAtlasSource(source, "2.0-draft");
3024
+ }
3025
+ function parseAtlasSource(source, outputVersion) {
3026
+ const lines = source.split(/\r?\n/);
3027
+ let sawSchemaHeader = false;
3028
+ let schemaVersion = "2.0";
3029
+ let system = null;
3030
+ let section = null;
3031
+ const objectNodes = [];
3032
+ let sawDefaults = false;
3033
+ let sawAtlas = false;
3034
+ const viewpointIds = /* @__PURE__ */ new Set();
3035
+ const annotationIds = /* @__PURE__ */ new Set();
3036
+ for (let index = 0; index < lines.length; index++) {
3037
+ const rawLine = lines[index];
3038
+ const lineNumber = index + 1;
3039
+ if (!rawLine.trim()) {
3040
+ continue;
3041
+ }
3042
+ const indent = getIndent(rawLine);
3043
+ const tokens = tokenizeLineDetailed(rawLine.slice(indent), {
3044
+ line: lineNumber,
3045
+ columnOffset: indent
3046
+ });
3047
+ if (tokens.length === 0) {
3048
+ continue;
3049
+ }
3050
+ if (!sawSchemaHeader) {
3051
+ schemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
3052
+ sawSchemaHeader = true;
3053
+ continue;
3054
+ }
3055
+ if (indent === 0) {
3056
+ section = startTopLevelSection(tokens, lineNumber, system, objectNodes, viewpointIds, annotationIds, {
3057
+ sawDefaults,
3058
+ sawAtlas
3059
+ });
3060
+ if (section.kind === "system") {
3061
+ system = section.system;
3062
+ } else if (section.kind === "defaults") {
3063
+ sawDefaults = true;
3064
+ } else if (section.kind === "atlas") {
3065
+ sawAtlas = true;
3066
+ }
3067
+ continue;
3068
+ }
3069
+ if (!section) {
3070
+ throw new WorldOrbitError("Indented line without parent atlas section", lineNumber, indent + 1);
3071
+ }
3072
+ handleSectionLine(section, indent, tokens, lineNumber);
3073
+ }
3074
+ if (!sawSchemaHeader) {
3075
+ throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
3076
+ }
3077
+ const ast = {
3078
+ type: "document",
3079
+ objects: objectNodes
3080
+ };
3081
+ const normalizedObjects = normalizeDocument(ast).objects;
3082
+ validateDocument({
3083
+ format: "worldorbit",
3084
+ version: "1.0",
3085
+ system: null,
3086
+ objects: normalizedObjects
3087
+ });
3088
+ const diagnostics = schemaVersion === "2.0-draft" && outputVersion === "2.0" ? [
3089
+ {
3090
+ code: "load.schema.deprecatedDraft",
3091
+ severity: "warning",
3092
+ source: "upgrade",
3093
+ message: 'Source header "schema 2.0-draft" is deprecated; canonical v2 documents now use "schema 2.0".'
3094
+ }
3095
+ ] : [];
3096
+ return {
3097
+ format: "worldorbit",
3098
+ version: outputVersion,
3099
+ sourceVersion: "1.0",
3100
+ system,
3101
+ objects: normalizedObjects,
3102
+ diagnostics
3103
+ };
3104
+ }
3105
+ function assertDraftSchemaHeader(tokens, line) {
3106
+ if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || tokens[1].value.toLowerCase() !== "2.0-draft" && tokens[1].value.toLowerCase() !== "2.0") {
3107
+ throw new WorldOrbitError('Expected atlas header "schema 2.0" or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
3108
+ }
3109
+ return tokens[1].value.toLowerCase() === "2.0-draft" ? "2.0-draft" : "2.0";
3110
+ }
3111
+ function startTopLevelSection(tokens, line, system, objectNodes, viewpointIds, annotationIds, flags) {
3112
+ const keyword = tokens[0]?.value.toLowerCase();
3113
+ switch (keyword) {
3114
+ case "system":
3115
+ if (system) {
3116
+ throw new WorldOrbitError('Atlas section "system" may only appear once', line, tokens[0].column);
3117
+ }
3118
+ return startSystemSection(tokens, line);
3119
+ case "defaults":
3120
+ if (!system) {
3121
+ throw new WorldOrbitError('Atlas section "defaults" requires a preceding system declaration', line, tokens[0].column);
3122
+ }
3123
+ if (flags.sawDefaults) {
3124
+ throw new WorldOrbitError('Atlas section "defaults" may only appear once', line, tokens[0].column);
3125
+ }
3126
+ return {
3127
+ kind: "defaults",
3128
+ system,
3129
+ seenFields: /* @__PURE__ */ new Set()
3130
+ };
3131
+ case "atlas":
3132
+ if (!system) {
3133
+ throw new WorldOrbitError('Atlas section "atlas" requires a preceding system declaration', line, tokens[0].column);
3134
+ }
3135
+ if (flags.sawAtlas) {
3136
+ throw new WorldOrbitError('Atlas section "atlas" may only appear once', line, tokens[0].column);
3137
+ }
3138
+ return {
3139
+ kind: "atlas",
3140
+ system,
3141
+ inMetadata: false,
3142
+ metadataIndent: null
3143
+ };
3144
+ case "viewpoint":
3145
+ if (!system) {
3146
+ throw new WorldOrbitError('Atlas section "viewpoint" requires a preceding system declaration', line, tokens[0].column);
3147
+ }
3148
+ return startViewpointSection(tokens, line, system, viewpointIds);
3149
+ case "annotation":
3150
+ if (!system) {
3151
+ throw new WorldOrbitError('Atlas section "annotation" requires a preceding system declaration', line, tokens[0].column);
3152
+ }
3153
+ return startAnnotationSection(tokens, line, system, annotationIds);
3154
+ case "object":
3155
+ return startObjectSection(tokens, line, objectNodes);
3156
+ default:
3157
+ throw new WorldOrbitError(`Unknown atlas section "${tokens[0]?.value ?? ""}"`, line, tokens[0]?.column ?? 1);
3158
+ }
3159
+ }
3160
+ function startSystemSection(tokens, line) {
3161
+ if (tokens.length !== 2) {
3162
+ throw new WorldOrbitError("Invalid atlas system declaration", line, tokens[0]?.column ?? 1);
3163
+ }
3164
+ const system = {
3165
+ type: "system",
3166
+ id: tokens[1].value,
3167
+ title: null,
3168
+ defaults: {
3169
+ view: "topdown",
3170
+ scale: null,
3171
+ units: null,
3172
+ preset: null,
3173
+ theme: null
3174
+ },
3175
+ atlasMetadata: {},
3176
+ viewpoints: [],
3177
+ annotations: []
3178
+ };
3179
+ return {
3180
+ kind: "system",
3181
+ system,
3182
+ seenFields: /* @__PURE__ */ new Set()
3183
+ };
3184
+ }
3185
+ function startViewpointSection(tokens, line, system, viewpointIds) {
3186
+ if (tokens.length !== 2) {
3187
+ throw new WorldOrbitError("Invalid viewpoint declaration", line, tokens[0]?.column ?? 1);
3188
+ }
3189
+ const id = normalizeIdentifier2(tokens[1].value);
3190
+ if (!id) {
3191
+ throw new WorldOrbitError("Viewpoint id must not be empty", line, tokens[1].column);
3192
+ }
3193
+ if (viewpointIds.has(id)) {
3194
+ throw new WorldOrbitError(`Duplicate viewpoint id "${id}"`, line, tokens[1].column);
3195
+ }
3196
+ const viewpoint = {
3197
+ id,
3198
+ label: humanizeIdentifier3(id),
3199
+ summary: "",
3200
+ focusObjectId: null,
3201
+ selectedObjectId: null,
3202
+ projection: system.defaults.view,
3203
+ preset: system.defaults.preset,
3204
+ zoom: null,
3205
+ rotationDeg: 0,
3206
+ layers: {},
3207
+ filter: null
3208
+ };
3209
+ system.viewpoints.push(viewpoint);
3210
+ viewpointIds.add(id);
3211
+ return {
3212
+ kind: "viewpoint",
3213
+ viewpoint,
3214
+ seenFields: /* @__PURE__ */ new Set(),
3215
+ inFilter: false,
3216
+ filterIndent: null,
3217
+ seenFilterFields: /* @__PURE__ */ new Set()
3218
+ };
3219
+ }
3220
+ function startAnnotationSection(tokens, line, system, annotationIds) {
3221
+ if (tokens.length !== 2) {
3222
+ throw new WorldOrbitError("Invalid annotation declaration", line, tokens[0]?.column ?? 1);
3223
+ }
3224
+ const id = normalizeIdentifier2(tokens[1].value);
3225
+ if (!id) {
3226
+ throw new WorldOrbitError("Annotation id must not be empty", line, tokens[1].column);
3227
+ }
3228
+ if (annotationIds.has(id)) {
3229
+ throw new WorldOrbitError(`Duplicate annotation id "${id}"`, line, tokens[1].column);
3230
+ }
3231
+ const annotation = {
3232
+ id,
3233
+ label: humanizeIdentifier3(id),
3234
+ targetObjectId: null,
3235
+ body: "",
3236
+ tags: [],
3237
+ sourceObjectId: null
3238
+ };
3239
+ system.annotations.push(annotation);
3240
+ annotationIds.add(id);
3241
+ return {
3242
+ kind: "annotation",
3243
+ annotation,
3244
+ seenFields: /* @__PURE__ */ new Set()
3245
+ };
3246
+ }
3247
+ function startObjectSection(tokens, line, objectNodes) {
3248
+ if (tokens.length < 3) {
3249
+ throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
3250
+ }
3251
+ const objectTypeToken = tokens[1];
3252
+ const idToken = tokens[2];
3253
+ const objectType = objectTypeToken.value;
3254
+ if (!WORLDORBIT_OBJECT_TYPES.has(objectType) || objectType === "system") {
3255
+ throw new WorldOrbitError(`Unknown object type "${objectTypeToken.value}"`, line, objectTypeToken.column);
3256
+ }
3257
+ const objectNode = {
3258
+ type: "object",
3259
+ objectType,
3260
+ name: idToken.value,
3261
+ inlineFields: parseInlineFields2(tokens.slice(3), line),
3262
+ blockFields: [],
3263
+ infoEntries: [],
3264
+ location: {
3265
+ line,
3266
+ column: objectTypeToken.column
3267
+ }
3268
+ };
3269
+ objectNodes.push(objectNode);
3270
+ return {
3271
+ kind: "object",
3272
+ objectNode,
3273
+ inInfoBlock: false,
3274
+ infoIndent: null
3275
+ };
3276
+ }
3277
+ function handleSectionLine(section, indent, tokens, line) {
3278
+ switch (section.kind) {
3279
+ case "system":
3280
+ applySystemField(section, tokens, line);
3281
+ return;
3282
+ case "defaults":
3283
+ applyDefaultsField(section, tokens, line);
3284
+ return;
3285
+ case "atlas":
3286
+ applyAtlasField(section, indent, tokens, line);
3287
+ return;
3288
+ case "viewpoint":
3289
+ applyViewpointField2(section, indent, tokens, line);
3290
+ return;
3291
+ case "annotation":
3292
+ applyAnnotationField(section, tokens, line);
3293
+ return;
3294
+ case "object":
3295
+ applyObjectField(section, indent, tokens, line);
3296
+ return;
3297
+ }
3298
+ }
3299
+ function applySystemField(section, tokens, line) {
3300
+ const key = requireUniqueField(tokens, section.seenFields, line);
3301
+ if (key !== "title") {
3302
+ throw new WorldOrbitError(`Unknown system atlas field "${tokens[0].value}"`, line, tokens[0].column);
3303
+ }
3304
+ section.system.title = joinFieldValue(tokens, line);
3305
+ }
3306
+ function applyDefaultsField(section, tokens, line) {
3307
+ const key = requireUniqueField(tokens, section.seenFields, line);
3308
+ const value = joinFieldValue(tokens, line);
3309
+ switch (key) {
3310
+ case "view":
3311
+ section.system.defaults.view = parseProjectionValue(value, line, tokens[0].column);
3312
+ return;
3313
+ case "scale":
3314
+ section.system.defaults.scale = value;
3315
+ return;
3316
+ case "units":
3317
+ section.system.defaults.units = value;
3318
+ return;
3319
+ case "preset":
3320
+ section.system.defaults.preset = parsePresetValue(value, line, tokens[0].column);
3321
+ return;
3322
+ case "theme":
3323
+ section.system.defaults.theme = value;
3324
+ return;
3325
+ default:
3326
+ throw new WorldOrbitError(`Unknown defaults field "${tokens[0].value}"`, line, tokens[0].column);
3327
+ }
3328
+ }
3329
+ function applyAtlasField(section, indent, tokens, line) {
3330
+ if (section.inMetadata && indent <= (section.metadataIndent ?? 0)) {
3331
+ section.inMetadata = false;
3332
+ section.metadataIndent = null;
3333
+ }
3334
+ if (section.inMetadata) {
3335
+ if (tokens.length < 2) {
3336
+ throw new WorldOrbitError("Invalid atlas metadata entry", line, tokens[0]?.column ?? 1);
3337
+ }
3338
+ const key = tokens[0].value;
3339
+ if (key in section.system.atlasMetadata) {
3340
+ throw new WorldOrbitError(`Duplicate atlas metadata key "${key}"`, line, tokens[0].column);
3341
+ }
3342
+ section.system.atlasMetadata[key] = joinFieldValue(tokens, line);
3343
+ return;
3344
+ }
3345
+ if (tokens.length === 1 && tokens[0].value.toLowerCase() === "metadata") {
3346
+ section.inMetadata = true;
3347
+ section.metadataIndent = indent;
3348
+ return;
3349
+ }
3350
+ throw new WorldOrbitError(`Unknown atlas field "${tokens[0].value}"`, line, tokens[0].column);
3351
+ }
3352
+ function applyViewpointField2(section, indent, tokens, line) {
3353
+ if (section.inFilter && indent <= (section.filterIndent ?? 0)) {
3354
+ section.inFilter = false;
3355
+ section.filterIndent = null;
3356
+ }
3357
+ if (section.inFilter) {
3358
+ applyViewpointFilterField(section, tokens, line);
3359
+ return;
3360
+ }
3361
+ if (tokens.length === 1 && tokens[0].value.toLowerCase() === "filter") {
3362
+ if (section.seenFields.has("filter")) {
3363
+ throw new WorldOrbitError('Duplicate viewpoint field "filter"', line, tokens[0].column);
3364
+ }
3365
+ section.seenFields.add("filter");
3366
+ section.inFilter = true;
3367
+ section.filterIndent = indent;
3368
+ return;
3369
+ }
3370
+ const key = requireUniqueField(tokens, section.seenFields, line);
3371
+ const value = joinFieldValue(tokens, line);
3372
+ switch (key) {
3373
+ case "label":
3374
+ section.viewpoint.label = value;
3375
+ return;
3376
+ case "summary":
3377
+ section.viewpoint.summary = value;
3378
+ return;
3379
+ case "focus":
3380
+ section.viewpoint.focusObjectId = value;
3381
+ return;
3382
+ case "select":
3383
+ section.viewpoint.selectedObjectId = value;
3384
+ return;
3385
+ case "projection":
3386
+ section.viewpoint.projection = parseProjectionValue(value, line, tokens[0].column);
3387
+ return;
3388
+ case "preset":
3389
+ section.viewpoint.preset = parsePresetValue(value, line, tokens[0].column);
3390
+ return;
3391
+ case "zoom":
3392
+ section.viewpoint.zoom = parsePositiveNumber2(value, line, tokens[0].column, "zoom");
3393
+ return;
3394
+ case "rotation":
3395
+ section.viewpoint.rotationDeg = parseFiniteNumber2(value, line, tokens[0].column, "rotation");
3396
+ return;
3397
+ case "layers":
3398
+ section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line);
3399
+ return;
3400
+ default:
3401
+ throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
3402
+ }
3403
+ }
3404
+ function applyViewpointFilterField(section, tokens, line) {
3405
+ const key = requireUniqueField(tokens, section.seenFilterFields, line);
3406
+ const filter = section.viewpoint.filter ?? createEmptyViewpointFilter2();
3407
+ switch (key) {
3408
+ case "query":
3409
+ filter.query = joinFieldValue(tokens, line);
3410
+ break;
3411
+ case "objecttypes":
3412
+ filter.objectTypes = parseObjectTypeTokens(tokens.slice(1), line);
3413
+ break;
3414
+ case "tags":
3415
+ filter.tags = parseTokenList(tokens.slice(1), line, "tags");
3416
+ break;
3417
+ case "groups":
3418
+ filter.groupIds = parseTokenList(tokens.slice(1), line, "groups");
3419
+ break;
3420
+ default:
3421
+ throw new WorldOrbitError(`Unknown viewpoint filter field "${tokens[0].value}"`, line, tokens[0].column);
3422
+ }
3423
+ section.viewpoint.filter = filter;
3424
+ }
3425
+ function applyAnnotationField(section, tokens, line) {
3426
+ const key = requireUniqueField(tokens, section.seenFields, line);
3427
+ switch (key) {
3428
+ case "label":
3429
+ section.annotation.label = joinFieldValue(tokens, line);
3430
+ return;
3431
+ case "target":
3432
+ section.annotation.targetObjectId = joinFieldValue(tokens, line);
3433
+ return;
3434
+ case "body":
3435
+ section.annotation.body = joinFieldValue(tokens, line);
3436
+ return;
3437
+ case "tags":
3438
+ section.annotation.tags = parseTokenList(tokens.slice(1), line, "tags");
3439
+ return;
3440
+ default:
3441
+ throw new WorldOrbitError(`Unknown annotation field "${tokens[0].value}"`, line, tokens[0].column);
3442
+ }
3443
+ }
3444
+ function applyObjectField(section, indent, tokens, line) {
3445
+ if (tokens.length === 1 && tokens[0].value === "info") {
3446
+ section.inInfoBlock = true;
3447
+ section.infoIndent = indent;
3448
+ return;
3449
+ }
3450
+ if (section.inInfoBlock && indent <= (section.infoIndent ?? 0)) {
3451
+ section.inInfoBlock = false;
3452
+ section.infoIndent = null;
3453
+ }
3454
+ if (section.inInfoBlock) {
3455
+ section.objectNode.infoEntries.push(parseInfoEntry2(tokens, line));
3456
+ return;
3457
+ }
3458
+ section.objectNode.blockFields.push(parseField2(tokens, line));
3459
+ }
3460
+ function requireUniqueField(tokens, seenFields, line) {
3461
+ if (tokens.length < 2) {
3462
+ throw new WorldOrbitError("Invalid atlas field line", line, tokens[0]?.column ?? 1);
3463
+ }
3464
+ const key = tokens[0].value.toLowerCase();
3465
+ if (seenFields.has(key)) {
3466
+ throw new WorldOrbitError(`Duplicate atlas field "${tokens[0].value}"`, line, tokens[0].column);
3467
+ }
3468
+ seenFields.add(key);
3469
+ return key;
3470
+ }
3471
+ function joinFieldValue(tokens, line) {
3472
+ if (tokens.length < 2) {
3473
+ throw new WorldOrbitError("Missing value for atlas field", line, tokens[0]?.column ?? 1);
3474
+ }
3475
+ return tokens.slice(1).map((token) => token.value).join(" ").trim();
3476
+ }
3477
+ function parseObjectTypeTokens(tokens, line) {
3478
+ if (tokens.length === 0) {
3479
+ throw new WorldOrbitError("Missing value for atlas field", line);
3480
+ }
3481
+ return tokens.map((token) => {
3482
+ const value = token.value;
3483
+ if (value !== "star" && value !== "planet" && value !== "moon" && value !== "belt" && value !== "asteroid" && value !== "comet" && value !== "ring" && value !== "structure" && value !== "phenomenon") {
3484
+ throw new WorldOrbitError(`Unknown viewpoint object type "${token.value}"`, line, token.column);
3485
+ }
3486
+ return value;
3487
+ });
3488
+ }
3489
+ function parseTokenList(tokens, line, field) {
3490
+ if (tokens.length === 0) {
3491
+ throw new WorldOrbitError(`Missing value for field "${field}"`, line);
3492
+ }
3493
+ return tokens.map((token) => token.value);
3494
+ }
3495
+ function parseLayerTokens(tokens, line) {
3496
+ if (tokens.length === 0) {
3497
+ throw new WorldOrbitError('Missing value for field "layers"', line);
3498
+ }
3499
+ const next = {};
3500
+ for (const token of tokens) {
3501
+ const enabled = !token.value.startsWith("-") && !token.value.startsWith("!");
3502
+ const rawLayer = token.value.replace(/^[-!]+/, "").toLowerCase();
3503
+ if (rawLayer === "orbits") {
3504
+ next["orbits-back"] = enabled;
3505
+ next["orbits-front"] = enabled;
3506
+ continue;
3507
+ }
3508
+ if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
3509
+ next[rawLayer] = enabled;
3510
+ continue;
3511
+ }
3512
+ throw new WorldOrbitError(`Unknown layer token "${token.value}"`, line, token.column);
3513
+ }
3514
+ return next;
3515
+ }
3516
+ function parseProjectionValue(value, line, column) {
3517
+ const normalized = value.toLowerCase();
3518
+ if (normalized === "topdown" || normalized === "isometric") {
3519
+ return normalized;
3520
+ }
3521
+ throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
3522
+ }
3523
+ function parsePresetValue(value, line, column) {
3524
+ const normalized = value.toLowerCase();
3525
+ if (normalized === "diagram" || normalized === "presentation" || normalized === "atlas-card" || normalized === "markdown") {
3526
+ return normalized;
3527
+ }
3528
+ throw new WorldOrbitError(`Unknown render preset "${value}"`, line, column);
3529
+ }
3530
+ function parsePositiveNumber2(value, line, column, field) {
3531
+ const parsed = Number(value);
3532
+ if (!Number.isFinite(parsed) || parsed <= 0) {
3533
+ throw new WorldOrbitError(`Field "${field}" expects a positive number`, line, column);
3534
+ }
3535
+ return parsed;
3536
+ }
3537
+ function parseFiniteNumber2(value, line, column, field) {
3538
+ const parsed = Number(value);
3539
+ if (!Number.isFinite(parsed)) {
3540
+ throw new WorldOrbitError(`Field "${field}" expects a finite number`, line, column);
3541
+ }
3542
+ return parsed;
3543
+ }
3544
+ function createEmptyViewpointFilter2() {
3545
+ return {
3546
+ query: null,
3547
+ objectTypes: [],
3548
+ tags: [],
3549
+ groupIds: []
3550
+ };
3551
+ }
3552
+ function parseInlineFields2(tokens, line) {
3553
+ const fields = [];
3554
+ let index = 0;
3555
+ while (index < tokens.length) {
3556
+ const keyToken = tokens[index];
3557
+ const schema = getFieldSchema(keyToken.value);
3558
+ if (!schema) {
3559
+ throw new WorldOrbitError(`Unknown field "${keyToken.value}"`, line, keyToken.column);
3560
+ }
3561
+ index++;
3562
+ const valueTokens = [];
3563
+ if (schema.arity === "multiple") {
3564
+ while (index < tokens.length && !isKnownFieldKey(tokens[index].value)) {
3565
+ valueTokens.push(tokens[index]);
3566
+ index++;
3567
+ }
3568
+ } else {
3569
+ const nextToken = tokens[index];
3570
+ if (nextToken) {
3571
+ valueTokens.push(nextToken);
3572
+ index++;
3573
+ }
3574
+ }
3575
+ if (valueTokens.length === 0) {
3576
+ throw new WorldOrbitError(`Missing value for field "${keyToken.value}"`, line, keyToken.column);
3577
+ }
3578
+ fields.push({
3579
+ type: "field",
3580
+ key: keyToken.value,
3581
+ values: valueTokens.map((token) => token.value),
3582
+ location: { line, column: keyToken.column }
3583
+ });
3584
+ }
3585
+ return fields;
3586
+ }
3587
+ function parseField2(tokens, line) {
3588
+ if (tokens.length < 2) {
3589
+ throw new WorldOrbitError("Invalid field line", line, tokens[0]?.column ?? 1);
3590
+ }
3591
+ if (!getFieldSchema(tokens[0].value)) {
3592
+ throw new WorldOrbitError(`Unknown field "${tokens[0].value}"`, line, tokens[0].column);
3593
+ }
3594
+ return {
3595
+ type: "field",
3596
+ key: tokens[0].value,
3597
+ values: tokens.slice(1).map((token) => token.value),
3598
+ location: { line, column: tokens[0].column }
3599
+ };
3600
+ }
3601
+ function parseInfoEntry2(tokens, line) {
3602
+ if (tokens.length < 2) {
3603
+ throw new WorldOrbitError("Invalid info entry", line, tokens[0]?.column ?? 1);
3604
+ }
3605
+ return {
3606
+ type: "info-entry",
3607
+ key: tokens[0].value,
3608
+ value: tokens.slice(1).map((token) => token.value).join(" "),
3609
+ location: { line, column: tokens[0].column }
3610
+ };
3611
+ }
3612
+ function normalizeIdentifier2(value) {
3613
+ return value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
3614
+ }
3615
+ function humanizeIdentifier3(value) {
3616
+ return value.split(/[-_]+/).filter(Boolean).map((segment) => segment[0].toUpperCase() + segment.slice(1)).join(" ");
3617
+ }
3618
+
3619
+ // packages/core/dist/atlas-edit.js
3620
+ function createEmptyAtlasDocument(systemId = "WorldOrbit") {
3621
+ return {
3622
+ format: "worldorbit",
3623
+ version: "2.0",
3624
+ sourceVersion: "1.0",
3625
+ system: {
3626
+ type: "system",
3627
+ id: systemId,
3628
+ title: systemId,
3629
+ defaults: {
3630
+ view: "topdown",
3631
+ scale: null,
3632
+ units: null,
3633
+ preset: null,
3634
+ theme: null
3635
+ },
3636
+ atlasMetadata: {},
3637
+ viewpoints: [],
3638
+ annotations: []
3639
+ },
3640
+ objects: [],
3641
+ diagnostics: []
3642
+ };
3643
+ }
3644
+ function cloneAtlasDocument(document) {
3645
+ return structuredClone(document);
3646
+ }
3647
+ function listAtlasDocumentPaths(document) {
3648
+ const paths = [{ kind: "system" }, { kind: "defaults" }];
3649
+ if (document.system) {
3650
+ for (const key of Object.keys(document.system.atlasMetadata).sort()) {
3651
+ paths.push({ kind: "metadata", key });
3652
+ }
3653
+ for (const viewpoint of [...document.system.viewpoints].sort(compareIdLike)) {
3654
+ paths.push({ kind: "viewpoint", id: viewpoint.id });
3655
+ }
3656
+ for (const annotation of [...document.system.annotations].sort(compareIdLike)) {
3657
+ paths.push({ kind: "annotation", id: annotation.id });
3658
+ }
3659
+ }
3660
+ for (const object of [...document.objects].sort(compareIdLike)) {
3661
+ paths.push({ kind: "object", id: object.id });
3662
+ }
3663
+ return paths;
3664
+ }
3665
+ function getAtlasDocumentNode(document, path) {
3666
+ switch (path.kind) {
3667
+ case "system":
3668
+ return document.system;
3669
+ case "defaults":
3670
+ return document.system?.defaults ?? null;
3671
+ case "metadata":
3672
+ return path.key ? document.system?.atlasMetadata[path.key] ?? null : null;
3673
+ case "object":
3674
+ return path.id ? findObject(document, path.id) : null;
3675
+ case "viewpoint":
3676
+ return path.id ? findViewpoint(document.system, path.id) : null;
3677
+ case "annotation":
3678
+ return path.id ? findAnnotation(document.system, path.id) : null;
3679
+ }
3680
+ }
3681
+ function upsertAtlasDocumentNode(document, path, value) {
3682
+ const next = cloneAtlasDocument(document);
3683
+ const system = ensureSystem(next);
3684
+ switch (path.kind) {
3685
+ case "system":
3686
+ next.system = value;
3687
+ return next;
3688
+ case "defaults":
3689
+ system.defaults = {
3690
+ ...system.defaults,
3691
+ ...value
3692
+ };
3693
+ return next;
3694
+ case "metadata":
3695
+ if (!path.key) {
3696
+ throw new Error('Metadata updates require a "key" value.');
3697
+ }
3698
+ if (value === null || value === void 0 || value === "") {
3699
+ delete system.atlasMetadata[path.key];
3700
+ } else {
3701
+ system.atlasMetadata[path.key] = String(value);
3702
+ }
3703
+ return next;
3704
+ case "object":
3705
+ if (!path.id) {
3706
+ throw new Error('Object updates require an "id" value.');
3707
+ }
3708
+ upsertById(next.objects, value);
3709
+ return next;
3710
+ case "viewpoint":
3711
+ if (!path.id) {
3712
+ throw new Error('Viewpoint updates require an "id" value.');
3713
+ }
3714
+ upsertById(system.viewpoints, value);
3715
+ return next;
3716
+ case "annotation":
3717
+ if (!path.id) {
3718
+ throw new Error('Annotation updates require an "id" value.');
3719
+ }
3720
+ upsertById(system.annotations, value);
3721
+ return next;
3722
+ }
3723
+ }
3724
+ function updateAtlasDocumentNode(document, path, updater) {
3725
+ return upsertAtlasDocumentNode(document, path, updater(getAtlasDocumentNode(document, path)));
3726
+ }
3727
+ function removeAtlasDocumentNode(document, path) {
3728
+ const next = cloneAtlasDocument(document);
3729
+ const system = ensureSystem(next);
3730
+ switch (path.kind) {
3731
+ case "metadata":
3732
+ if (path.key) {
3733
+ delete system.atlasMetadata[path.key];
3734
+ }
3735
+ return next;
3736
+ case "object":
3737
+ if (path.id) {
3738
+ next.objects = next.objects.filter((object) => object.id !== path.id);
3739
+ }
3740
+ return next;
3741
+ case "viewpoint":
3742
+ if (path.id) {
3743
+ system.viewpoints = system.viewpoints.filter((viewpoint) => viewpoint.id !== path.id);
3744
+ }
3745
+ return next;
3746
+ case "annotation":
3747
+ if (path.id) {
3748
+ system.annotations = system.annotations.filter((annotation) => annotation.id !== path.id);
3749
+ }
3750
+ return next;
3751
+ default:
3752
+ return next;
3753
+ }
3754
+ }
3755
+ function resolveAtlasDiagnostics(document, diagnostics) {
3756
+ return diagnostics.map((diagnostic) => ({
3757
+ diagnostic,
3758
+ path: resolveAtlasDiagnosticPath(document, diagnostic)
3759
+ }));
3760
+ }
3761
+ function resolveAtlasDiagnosticPath(document, diagnostic) {
3762
+ if (diagnostic.objectId && findObject(document, diagnostic.objectId)) {
3763
+ return {
3764
+ kind: "object",
3765
+ id: diagnostic.objectId
3766
+ };
3767
+ }
3768
+ if (diagnostic.field?.startsWith("viewpoint.")) {
3769
+ const parts = diagnostic.field.split(".");
3770
+ if (parts[1] && findViewpoint(document.system, parts[1])) {
3771
+ return {
3772
+ kind: "viewpoint",
3773
+ id: parts[1]
3774
+ };
3775
+ }
3776
+ }
3777
+ if (diagnostic.field?.startsWith("annotation.")) {
3778
+ const parts = diagnostic.field.split(".");
3779
+ if (parts[1] && findAnnotation(document.system, parts[1])) {
3780
+ return {
3781
+ kind: "annotation",
3782
+ id: parts[1]
3783
+ };
3784
+ }
3785
+ }
3786
+ if (diagnostic.field && diagnostic.field in ensureSystem(document).atlasMetadata) {
3787
+ return {
3788
+ kind: "metadata",
3789
+ key: diagnostic.field
3790
+ };
3791
+ }
3792
+ return null;
3793
+ }
3794
+ function validateAtlasDocumentWithDiagnostics(document) {
3795
+ const materialized = materializeAtlasDocument(document);
3796
+ const result = validateDocumentWithDiagnostics(materialized);
3797
+ return resolveAtlasDiagnostics(document, result.diagnostics);
3798
+ }
3799
+ function ensureSystem(document) {
3800
+ if (document.system) {
3801
+ return document.system;
3802
+ }
3803
+ document.system = createEmptyAtlasDocument().system;
3804
+ return document.system;
3805
+ }
3806
+ function findObject(document, objectId) {
3807
+ return document.objects.find((object) => object.id === objectId) ?? null;
3808
+ }
3809
+ function findViewpoint(system, viewpointId) {
3810
+ return system?.viewpoints.find((viewpoint) => viewpoint.id === viewpointId) ?? null;
3811
+ }
3812
+ function findAnnotation(system, annotationId) {
3813
+ return system?.annotations.find((annotation) => annotation.id === annotationId) ?? null;
3814
+ }
3815
+ function upsertById(items, value) {
3816
+ const index = items.findIndex((item) => item.id === value.id);
3817
+ if (index === -1) {
3818
+ items.push(value);
3819
+ items.sort(compareIdLike);
3820
+ return;
3821
+ }
3822
+ items[index] = value;
3823
+ }
3824
+ function compareIdLike(left, right) {
3825
+ return left.id.localeCompare(right.id);
3826
+ }
3827
+
3828
+ // packages/core/dist/load.js
3829
+ var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0)?$/i;
3830
+ var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
3831
+ function detectWorldOrbitSchemaVersion(source) {
3832
+ for (const line of source.split(/\r?\n/)) {
3833
+ const trimmed = line.trim();
3834
+ if (!trimmed) {
3835
+ continue;
3836
+ }
3837
+ if (LEGACY_DRAFT_SCHEMA_PATTERN.test(trimmed)) {
3838
+ return "2.0-draft";
3839
+ }
3840
+ if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
3841
+ return "2.0";
3842
+ }
3843
+ return "1.0";
3844
+ }
3845
+ return "1.0";
3846
+ }
3847
+ function loadWorldOrbitSource(source) {
3848
+ const result = loadWorldOrbitSourceWithDiagnostics(source);
3849
+ if (!result.ok || !result.value) {
3850
+ const diagnostic = result.diagnostics[0];
3851
+ throw new WorldOrbitError(diagnostic?.message ?? "Failed to load WorldOrbit source", diagnostic?.line, diagnostic?.column);
3852
+ }
3853
+ return result.value;
3854
+ }
3855
+ function loadWorldOrbitSourceWithDiagnostics(source) {
3856
+ const schemaVersion = detectWorldOrbitSchemaVersion(source);
3857
+ if (schemaVersion === "2.0" || schemaVersion === "2.0-draft") {
3858
+ return loadAtlasSourceWithDiagnostics(source, schemaVersion);
3859
+ }
3860
+ let ast;
3861
+ try {
3862
+ ast = parseWorldOrbit(source);
3863
+ } catch (error) {
3864
+ return {
3865
+ ok: false,
3866
+ value: null,
3867
+ diagnostics: [diagnosticFromError(error, "parse")]
3868
+ };
3869
+ }
3870
+ let document;
3871
+ try {
3872
+ document = normalizeDocument(ast);
3873
+ } catch (error) {
3874
+ return {
3875
+ ok: false,
3876
+ value: null,
3877
+ diagnostics: [diagnosticFromError(error, "normalize")]
3878
+ };
3879
+ }
3880
+ try {
3881
+ validateDocument(document);
3882
+ } catch (error) {
3883
+ return {
3884
+ ok: false,
3885
+ value: null,
3886
+ diagnostics: [diagnosticFromError(error, "validate")]
3887
+ };
3888
+ }
3889
+ return {
3890
+ ok: true,
3891
+ value: {
3892
+ schemaVersion,
3893
+ ast,
3894
+ document,
3895
+ atlasDocument: null,
3896
+ draftDocument: null,
3897
+ diagnostics: []
3898
+ },
3899
+ diagnostics: []
3900
+ };
3901
+ }
3902
+ function loadAtlasSourceWithDiagnostics(source, schemaVersion) {
3903
+ let atlasDocument;
3904
+ try {
3905
+ atlasDocument = parseWorldOrbitAtlas(source);
3906
+ } catch (error) {
3907
+ return {
3908
+ ok: false,
3909
+ value: null,
3910
+ diagnostics: [diagnosticFromError(error, "parse", "load.atlas.failed")]
3911
+ };
3912
+ }
3913
+ let document;
3914
+ try {
3915
+ document = materializeAtlasDocument(atlasDocument);
3916
+ } catch (error) {
3917
+ return {
3918
+ ok: false,
3919
+ value: null,
3920
+ diagnostics: [diagnosticFromError(error, "normalize", "load.atlas.materialize.failed")]
3921
+ };
3922
+ }
3923
+ try {
3924
+ validateDocument(document);
3925
+ } catch (error) {
3926
+ return {
3927
+ ok: false,
3928
+ value: null,
3929
+ diagnostics: [diagnosticFromError(error, "validate", "load.atlas.validate.failed")]
3930
+ };
3931
+ }
3932
+ const loaded = {
3933
+ schemaVersion,
3934
+ ast: null,
3935
+ document,
3936
+ atlasDocument,
3937
+ draftDocument: atlasDocument,
3938
+ diagnostics: [...atlasDocument.diagnostics]
3939
+ };
3940
+ return {
3941
+ ok: true,
3942
+ value: loaded,
3943
+ diagnostics: [...atlasDocument.diagnostics]
3944
+ };
3945
+ }
3946
+
3947
+ // packages/core/dist/markdown.js
3948
+ var FENCE_PATTERN = /^```worldorbit(?:\s+(.*))?\s*$/;
3949
+ function extractWorldOrbitBlocks(markdown) {
3950
+ const lines = markdown.split(/\r?\n/);
3951
+ const blocks = [];
3952
+ let active = false;
3953
+ let activeInfo = null;
3954
+ let activeStartLine = 0;
3955
+ let buffer = [];
3956
+ lines.forEach((line, index) => {
3957
+ const lineNumber = index + 1;
3958
+ if (!active) {
3959
+ const match = line.match(FENCE_PATTERN);
3960
+ if (match) {
3961
+ active = true;
3962
+ activeInfo = match[1] ?? null;
3963
+ activeStartLine = lineNumber;
3964
+ buffer = [];
3965
+ }
3966
+ return;
3967
+ }
3968
+ if (line.trim() === "```") {
3969
+ blocks.push({
3970
+ source: buffer.join("\n"),
3971
+ info: activeInfo,
3972
+ startLine: activeStartLine,
3973
+ endLine: lineNumber
3974
+ });
3975
+ active = false;
3976
+ activeInfo = null;
3977
+ activeStartLine = 0;
3978
+ buffer = [];
3979
+ return;
3980
+ }
3981
+ buffer.push(line);
3982
+ });
3983
+ return blocks;
3984
+ }
3985
+
3986
+ // packages/core/dist/index.js
3987
+ function parse(source) {
3988
+ const ast = parseWorldOrbit(source);
3989
+ const document = normalizeDocument(ast);
3990
+ validateDocument(document);
3991
+ return { ast, document };
3992
+ }
3993
+ function render(source) {
3994
+ const result = parse(source);
3995
+ return {
3996
+ ...result,
3997
+ scene: renderDocumentToScene(result.document)
3998
+ };
3999
+ }
4000
+ function load(source) {
4001
+ return loadWorldOrbitSource(source);
4002
+ }
4003
+ function parseSafe(source) {
4004
+ return parseWithDiagnostics(source);
4005
+ }
4006
+ function stringify(document, options = {}) {
4007
+ return formatDocument(document, options);
4008
+ }
4009
+ })();