worldorbit 3.2.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.md +546 -545
  2. package/dist/browser/core/dist/atlas-edit.js +146 -1
  3. package/dist/browser/core/dist/atlas-validate.js +105 -10
  4. package/dist/browser/core/dist/draft-parse.js +341 -16
  5. package/dist/browser/core/dist/draft.d.ts +2 -1
  6. package/dist/browser/core/dist/draft.js +25 -3
  7. package/dist/browser/core/dist/format.js +86 -4
  8. package/dist/browser/core/dist/index.d.ts +1 -0
  9. package/dist/browser/core/dist/index.js +1 -0
  10. package/dist/browser/core/dist/load.js +7 -2
  11. package/dist/browser/core/dist/normalize.js +1 -0
  12. package/dist/browser/core/dist/scene.js +11 -2
  13. package/dist/browser/core/dist/schema.js +11 -1
  14. package/dist/browser/core/dist/solver.d.ts +26 -0
  15. package/dist/browser/core/dist/solver.js +27 -0
  16. package/dist/browser/core/dist/types.d.ts +57 -3
  17. package/dist/browser/editor/dist/editor.js +844 -719
  18. package/dist/browser/editor/dist/types.d.ts +2 -1
  19. package/dist/browser/obsidian-plugin/dist/diagnostics.d.ts +3 -0
  20. package/dist/browser/obsidian-plugin/dist/diagnostics.js +23 -0
  21. package/dist/browser/obsidian-plugin/dist/examples.d.ts +3 -0
  22. package/dist/browser/obsidian-plugin/dist/examples.js +77 -0
  23. package/dist/browser/obsidian-plugin/dist/index.d.ts +9 -0
  24. package/dist/browser/obsidian-plugin/dist/index.js +8 -0
  25. package/dist/browser/obsidian-plugin/dist/main.d.ts +2 -0
  26. package/dist/browser/obsidian-plugin/dist/main.js +2 -0
  27. package/dist/browser/obsidian-plugin/dist/navigation.d.ts +6 -0
  28. package/dist/browser/obsidian-plugin/dist/navigation.js +44 -0
  29. package/dist/browser/obsidian-plugin/dist/plugin.d.ts +8 -0
  30. package/dist/browser/obsidian-plugin/dist/plugin.js +508 -0
  31. package/dist/browser/obsidian-plugin/dist/positions.d.ts +7 -0
  32. package/dist/browser/obsidian-plugin/dist/positions.js +14 -0
  33. package/dist/browser/obsidian-plugin/dist/settings.d.ts +2 -0
  34. package/dist/browser/obsidian-plugin/dist/settings.js +5 -0
  35. package/dist/browser/obsidian-plugin/dist/theme.d.ts +2 -0
  36. package/dist/browser/obsidian-plugin/dist/theme.js +31 -0
  37. package/dist/browser/obsidian-plugin/dist/types.d.ts +42 -0
  38. package/dist/browser/obsidian-plugin/dist/types.js +1 -0
  39. package/dist/browser/obsidian-plugin/dist/viewer-host.d.ts +14 -0
  40. package/dist/browser/obsidian-plugin/dist/viewer-host.js +110 -0
  41. package/dist/browser/viewer/dist/index.d.ts +1 -0
  42. package/dist/browser/viewer/dist/index.js +1 -0
  43. package/dist/browser/viewer/dist/interactive-2d.d.ts +21 -0
  44. package/dist/browser/viewer/dist/interactive-2d.js +201 -0
  45. package/dist/browser/viewer/dist/minimap.js +9 -7
  46. package/dist/browser/viewer/dist/render.d.ts +1 -1
  47. package/dist/browser/viewer/dist/render.js +25 -20
  48. package/dist/browser/viewer/dist/runtime-3d.js +2 -0
  49. package/dist/browser/viewer/dist/viewer-state.d.ts +1 -1
  50. package/dist/browser/viewer/dist/viewer-state.js +1 -1
  51. package/dist/browser/viewer/dist/viewer.js +7 -3
  52. package/dist/obsidian-plugin/LICENSE +21 -0
  53. package/dist/obsidian-plugin/README.md +141 -0
  54. package/dist/obsidian-plugin/main.js +108 -0
  55. package/dist/obsidian-plugin/manifest.json +9 -0
  56. package/dist/obsidian-plugin/obsidian_scsh_1.png +0 -0
  57. package/dist/obsidian-plugin/obsidian_scsh_2.png +0 -0
  58. package/dist/obsidian-plugin/styles.css +164 -0
  59. package/dist/unpkg/core/dist/atlas-edit.js +146 -1
  60. package/dist/unpkg/core/dist/atlas-validate.js +105 -10
  61. package/dist/unpkg/core/dist/draft-parse.js +341 -16
  62. package/dist/unpkg/core/dist/draft.d.ts +2 -1
  63. package/dist/unpkg/core/dist/draft.js +25 -3
  64. package/dist/unpkg/core/dist/format.js +86 -4
  65. package/dist/unpkg/core/dist/index.d.ts +1 -0
  66. package/dist/unpkg/core/dist/index.js +1 -0
  67. package/dist/unpkg/core/dist/load.js +7 -2
  68. package/dist/unpkg/core/dist/normalize.js +1 -0
  69. package/dist/unpkg/core/dist/scene.js +11 -2
  70. package/dist/unpkg/core/dist/schema.js +11 -1
  71. package/dist/unpkg/core/dist/solver.d.ts +26 -0
  72. package/dist/unpkg/core/dist/solver.js +27 -0
  73. package/dist/unpkg/core/dist/types.d.ts +57 -3
  74. package/dist/unpkg/editor/dist/editor.js +844 -719
  75. package/dist/unpkg/editor/dist/types.d.ts +2 -1
  76. package/dist/unpkg/obsidian-plugin/dist/diagnostics.d.ts +3 -0
  77. package/dist/unpkg/obsidian-plugin/dist/diagnostics.js +23 -0
  78. package/dist/unpkg/obsidian-plugin/dist/examples.d.ts +3 -0
  79. package/dist/unpkg/obsidian-plugin/dist/examples.js +77 -0
  80. package/dist/unpkg/obsidian-plugin/dist/index.d.ts +9 -0
  81. package/dist/unpkg/obsidian-plugin/dist/index.js +8 -0
  82. package/dist/unpkg/obsidian-plugin/dist/main.d.ts +2 -0
  83. package/dist/unpkg/obsidian-plugin/dist/main.js +2 -0
  84. package/dist/unpkg/obsidian-plugin/dist/navigation.d.ts +6 -0
  85. package/dist/unpkg/obsidian-plugin/dist/navigation.js +44 -0
  86. package/dist/unpkg/obsidian-plugin/dist/plugin.d.ts +8 -0
  87. package/dist/unpkg/obsidian-plugin/dist/plugin.js +508 -0
  88. package/dist/unpkg/obsidian-plugin/dist/positions.d.ts +7 -0
  89. package/dist/unpkg/obsidian-plugin/dist/positions.js +14 -0
  90. package/dist/unpkg/obsidian-plugin/dist/settings.d.ts +2 -0
  91. package/dist/unpkg/obsidian-plugin/dist/settings.js +5 -0
  92. package/dist/unpkg/obsidian-plugin/dist/theme.d.ts +2 -0
  93. package/dist/unpkg/obsidian-plugin/dist/theme.js +31 -0
  94. package/dist/unpkg/obsidian-plugin/dist/types.d.ts +42 -0
  95. package/dist/unpkg/obsidian-plugin/dist/types.js +1 -0
  96. package/dist/unpkg/obsidian-plugin/dist/viewer-host.d.ts +14 -0
  97. package/dist/unpkg/obsidian-plugin/dist/viewer-host.js +110 -0
  98. package/dist/unpkg/viewer/dist/index.d.ts +1 -0
  99. package/dist/unpkg/viewer/dist/index.js +1 -0
  100. package/dist/unpkg/viewer/dist/interactive-2d.d.ts +21 -0
  101. package/dist/unpkg/viewer/dist/interactive-2d.js +201 -0
  102. package/dist/unpkg/viewer/dist/minimap.js +9 -7
  103. package/dist/unpkg/viewer/dist/render.d.ts +1 -1
  104. package/dist/unpkg/viewer/dist/render.js +25 -20
  105. package/dist/unpkg/viewer/dist/runtime-3d.js +2 -0
  106. package/dist/unpkg/viewer/dist/viewer-state.d.ts +1 -1
  107. package/dist/unpkg/viewer/dist/viewer-state.js +1 -1
  108. package/dist/unpkg/viewer/dist/viewer.js +7 -3
  109. package/dist/unpkg/worldorbit-core.min.js +10 -10
  110. package/dist/unpkg/worldorbit-editor.min.js +359 -332
  111. package/dist/unpkg/worldorbit-markdown.min.js +28 -28
  112. package/dist/unpkg/worldorbit-viewer.min.js +203 -203
  113. package/dist/unpkg/worldorbit.js +958 -40
  114. package/dist/unpkg/worldorbit.min.js +214 -214
  115. package/package.json +22 -1
  116. package/packages/core/dist/atlas-edit.js +146 -1
  117. package/packages/core/dist/atlas-validate.js +105 -10
  118. package/packages/core/dist/draft-parse.js +341 -16
  119. package/packages/core/dist/draft.d.ts +2 -1
  120. package/packages/core/dist/draft.js +25 -3
  121. package/packages/core/dist/format.js +86 -4
  122. package/packages/core/dist/index.d.ts +1 -0
  123. package/packages/core/dist/index.js +1 -0
  124. package/packages/core/dist/load.js +7 -2
  125. package/packages/core/dist/normalize.js +1 -0
  126. package/packages/core/dist/scene.js +11 -2
  127. package/packages/core/dist/schema.js +11 -1
  128. package/packages/core/dist/solver.d.ts +26 -0
  129. package/packages/core/dist/solver.js +27 -0
  130. package/packages/core/dist/types.d.ts +57 -3
  131. package/packages/editor/dist/editor.js +844 -719
  132. package/packages/editor/dist/types.d.ts +2 -1
  133. package/packages/viewer/dist/index.d.ts +1 -0
  134. package/packages/viewer/dist/index.js +1 -0
  135. package/packages/viewer/dist/interactive-2d.d.ts +21 -0
  136. package/packages/viewer/dist/interactive-2d.js +201 -0
  137. package/packages/viewer/dist/minimap.js +9 -7
  138. package/packages/viewer/dist/render.d.ts +1 -1
  139. package/packages/viewer/dist/render.js +25 -20
  140. package/packages/viewer/dist/runtime-3d.js +2 -0
  141. package/packages/viewer/dist/viewer-state.d.ts +1 -1
  142. package/packages/viewer/dist/viewer-state.js +1 -1
  143. package/packages/viewer/dist/viewer.js +7 -3
@@ -30712,6 +30712,8 @@ void main() {
30712
30712
  createEmbedPayload: () => createEmbedPayload,
30713
30713
  createEmptyAtlasDocument: () => createEmptyAtlasDocument,
30714
30714
  createInteractiveViewer: () => createInteractiveViewer,
30715
+ createInteractiveViewer2D: () => createInteractiveViewer2D,
30716
+ createTrajectorySolverSnapshot: () => createTrajectorySolverSnapshot,
30715
30717
  createWorldOrbitEmbedMarkup: () => createWorldOrbitEmbedMarkup,
30716
30718
  defineWorldOrbitViewerElement: () => defineWorldOrbitViewerElement,
30717
30719
  deserializeViewerAtlasState: () => deserializeViewerAtlasState,
@@ -30809,6 +30811,7 @@ void main() {
30809
30811
  "asteroid",
30810
30812
  "comet",
30811
30813
  "ring",
30814
+ "craft",
30812
30815
  "structure",
30813
30816
  "phenomenon"
30814
30817
  ];
@@ -30819,10 +30822,11 @@ void main() {
30819
30822
  "moon",
30820
30823
  "asteroid",
30821
30824
  "comet",
30825
+ "craft",
30822
30826
  "structure",
30823
30827
  "phenomenon"
30824
30828
  ];
30825
- var ANCHORED_OBJECTS = ["structure", "phenomenon"];
30829
+ var ANCHORED_OBJECTS = ["craft", "structure", "phenomenon"];
30826
30830
  var ORBITAL_OBJECTS = [
30827
30831
  "star",
30828
30832
  "planet",
@@ -30831,6 +30835,7 @@ void main() {
30831
30835
  "asteroid",
30832
30836
  "comet",
30833
30837
  "ring",
30838
+ "craft",
30834
30839
  "structure",
30835
30840
  "phenomenon"
30836
30841
  ];
@@ -30842,6 +30847,7 @@ void main() {
30842
30847
  "asteroid",
30843
30848
  "comet",
30844
30849
  "ring",
30850
+ "craft",
30845
30851
  "structure",
30846
30852
  "phenomenon"
30847
30853
  ];
@@ -31070,6 +31076,12 @@ void main() {
31070
31076
  arity: "single",
31071
31077
  objectTypes: NON_SYSTEM_OBJECTS,
31072
31078
  unitFamily: "duration"
31079
+ }),
31080
+ createField("trajectory", {
31081
+ kind: "string",
31082
+ placement: false,
31083
+ arity: "single",
31084
+ objectTypes: ["craft", "structure"]
31073
31085
  })
31074
31086
  ].map((schema) => [schema.key, schema]));
31075
31087
  var WORLDORBIT_FIELD_KEYS = new Set(WORLDORBIT_FIELD_SCHEMAS.keys());
@@ -31383,6 +31395,7 @@ void main() {
31383
31395
  groups: [],
31384
31396
  relations: [],
31385
31397
  events: [],
31398
+ trajectories: [],
31386
31399
  objects
31387
31400
  };
31388
31401
  }
@@ -32303,7 +32316,7 @@ void main() {
32303
32316
  anchorX,
32304
32317
  anchorY,
32305
32318
  label: object.id,
32306
- secondaryLabel: object.type === "structure" ? String(object.properties.kind ?? object.type) : object.type,
32319
+ secondaryLabel: object.type === "structure" || object.type === "craft" ? String(object.properties.kind ?? object.type) : object.type,
32307
32320
  fillColor: customColorFor(object.properties.color),
32308
32321
  imageHref: typeof object.properties.image === "string" && object.properties.image.trim() ? object.properties.image : void 0,
32309
32322
  hidden: object.properties.hidden === true
@@ -32397,6 +32410,7 @@ void main() {
32397
32410
  case "asteroid":
32398
32411
  case "comet":
32399
32412
  return 4;
32413
+ case "craft":
32400
32414
  case "structure":
32401
32415
  case "phenomenon":
32402
32416
  return 5;
@@ -32421,7 +32435,7 @@ void main() {
32421
32435
  const oppositeVertical = vertical === "below" ? "above" : "below";
32422
32436
  const horizontal = defaultHorizontalDirection(object, parent, sceneWidth);
32423
32437
  const oppositeHorizontal = horizontal === "right" ? "left" : "right";
32424
- const preferHorizontal = object.object.type === "structure" || object.object.type === "phenomenon" || object.object.placement?.mode === "at" || object.object.placement?.mode === "surface" || object.object.placement?.mode === "free";
32438
+ const preferHorizontal = object.object.type === "craft" || object.object.type === "structure" || object.object.type === "phenomenon" || object.object.placement?.mode === "at" || object.object.placement?.mode === "surface" || object.object.placement?.mode === "free";
32425
32439
  return preferHorizontal ? [horizontal, vertical, oppositeHorizontal, oppositeVertical] : [vertical, horizontal, oppositeVertical, oppositeHorizontal];
32426
32440
  }
32427
32441
  function defaultVerticalDirection(object, parent, sceneHeight) {
@@ -32885,7 +32899,7 @@ void main() {
32885
32899
  return next;
32886
32900
  }
32887
32901
  function parseViewpointObjectTypes(value) {
32888
- return splitListValue(value).filter((entry) => entry === "star" || entry === "planet" || entry === "moon" || entry === "belt" || entry === "asteroid" || entry === "comet" || entry === "ring" || entry === "structure" || entry === "phenomenon");
32902
+ return splitListValue(value).filter((entry) => entry === "star" || entry === "planet" || entry === "moon" || entry === "belt" || entry === "asteroid" || entry === "comet" || entry === "ring" || entry === "craft" || entry === "structure" || entry === "phenomenon");
32889
32903
  }
32890
32904
  function parseViewpointGroups(value, document2, relationships, objectMap) {
32891
32905
  return splitListValue(value).map((entry) => {
@@ -33506,6 +33520,8 @@ void main() {
33506
33520
  return clampNumber(6 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
33507
33521
  case "ring":
33508
33522
  return clampNumber(5 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
33523
+ case "craft":
33524
+ return clampNumber(5 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
33509
33525
  case "structure":
33510
33526
  return clampNumber(6 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
33511
33527
  case "phenomenon":
@@ -33519,6 +33535,8 @@ void main() {
33519
33535
  return radius * 2.4;
33520
33536
  case "phenomenon":
33521
33537
  return radius * 1.25;
33538
+ case "craft":
33539
+ return radius + 1.5;
33522
33540
  case "structure":
33523
33541
  return radius + 2;
33524
33542
  default:
@@ -34084,6 +34102,35 @@ void main() {
34084
34102
  return value * Math.PI / 180;
34085
34103
  }
34086
34104
 
34105
+ // packages/core/dist/solver.js
34106
+ function createTrajectorySolverSnapshot(trajectory) {
34107
+ return {
34108
+ trajectoryId: trajectory.id,
34109
+ craftObjectId: trajectory.craftObjectId,
34110
+ segments: trajectory.segments.map((segment) => ({
34111
+ segmentId: segment.id,
34112
+ kind: segment.kind,
34113
+ fromObjectId: segment.fromObjectId,
34114
+ toObjectId: segment.toObjectId,
34115
+ aroundObjectId: segment.aroundObjectId,
34116
+ assistObjectId: segment.assist?.objectId ?? null,
34117
+ duration: segment.duration ?? null,
34118
+ deltaV: segment.deltaV ?? null
34119
+ })),
34120
+ maneuvers: trajectory.segments.flatMap((segment) => segment.maneuvers.map((maneuver) => mapManeuver(segment.id, maneuver)))
34121
+ };
34122
+ }
34123
+ function mapManeuver(segmentId, maneuver) {
34124
+ return {
34125
+ segmentId,
34126
+ maneuverId: maneuver.id,
34127
+ kind: maneuver.kind,
34128
+ epoch: maneuver.epoch,
34129
+ deltaV: maneuver.deltaV ?? null,
34130
+ duration: maneuver.duration ?? null
34131
+ };
34132
+ }
34133
+
34087
34134
  // packages/core/dist/draft.js
34088
34135
  function upgradeDocumentToV2(document2, options = {}) {
34089
34136
  const scene = renderDocumentToScene(document2, options);
@@ -34102,15 +34149,16 @@ void main() {
34102
34149
  }
34103
34150
  return {
34104
34151
  format: "worldorbit",
34105
- version: "2.6",
34106
- schemaVersion: "2.6",
34152
+ version: "3.0",
34153
+ schemaVersion: "3.0",
34107
34154
  sourceVersion: document2.version,
34108
34155
  theme: document2.theme ?? null,
34109
34156
  system,
34110
34157
  groups: structuredClone(document2.groups ?? []),
34111
34158
  relations: structuredClone(document2.relations ?? []),
34112
34159
  events: structuredClone(document2.events ?? []),
34113
- objects: document2.objects.map(cloneWorldOrbitObject),
34160
+ trajectories: [],
34161
+ objects: document2.objects.map(cloneWorldOrbitObject).map(normalizeLegacyCraftObject),
34114
34162
  diagnostics
34115
34163
  };
34116
34164
  }
@@ -34139,6 +34187,7 @@ void main() {
34139
34187
  groups: structuredClone(document2.groups ?? []),
34140
34188
  relations: structuredClone(document2.relations ?? []),
34141
34189
  events: document2.events.map(cloneWorldOrbitEvent),
34190
+ trajectories: document2.trajectories.map(cloneWorldOrbitTrajectory),
34142
34191
  objects
34143
34192
  };
34144
34193
  }
@@ -34286,6 +34335,7 @@ void main() {
34286
34335
  function cloneWorldOrbitObject(object) {
34287
34336
  return {
34288
34337
  ...object,
34338
+ trajectoryId: object.trajectoryId ?? null,
34289
34339
  groups: object.groups ? [...object.groups] : void 0,
34290
34340
  resonance: object.resonance ? { ...object.resonance } : object.resonance,
34291
34341
  renderHints: object.renderHints ? { ...object.renderHints } : object.renderHints,
@@ -34305,6 +34355,7 @@ void main() {
34305
34355
  function cloneWorldOrbitEvent(event) {
34306
34356
  return {
34307
34357
  ...event,
34358
+ trajectoryId: event.trajectoryId ?? null,
34308
34359
  participantObjectIds: [...event.participantObjectIds],
34309
34360
  tags: [...event.tags],
34310
34361
  positions: event.positions.map(cloneWorldOrbitEventPose)
@@ -34314,12 +34365,30 @@ void main() {
34314
34365
  return {
34315
34366
  objectId: pose.objectId,
34316
34367
  placement: clonePlacement(pose.placement),
34368
+ trajectorySegmentId: pose.trajectorySegmentId ?? null,
34369
+ trajectoryManeuverId: pose.trajectoryManeuverId ?? null,
34317
34370
  inner: pose.inner ? { ...pose.inner } : void 0,
34318
34371
  outer: pose.outer ? { ...pose.outer } : void 0,
34319
34372
  epoch: pose.epoch ?? null,
34320
34373
  referencePlane: pose.referencePlane ?? null
34321
34374
  };
34322
34375
  }
34376
+ function cloneWorldOrbitTrajectory(trajectory) {
34377
+ return structuredClone(trajectory);
34378
+ }
34379
+ function normalizeLegacyCraftObject(object) {
34380
+ if (object.type !== "structure") {
34381
+ return object;
34382
+ }
34383
+ const kind = typeof object.properties.kind === "string" ? object.properties.kind.toLowerCase() : "";
34384
+ if (!["ship", "probe", "station"].includes(kind)) {
34385
+ return object;
34386
+ }
34387
+ return {
34388
+ ...object,
34389
+ type: "craft"
34390
+ };
34391
+ }
34323
34392
  function clonePlacement(placement) {
34324
34393
  return placement ? structuredClone(placement) : null;
34325
34394
  }
@@ -34560,7 +34629,7 @@ void main() {
34560
34629
  ];
34561
34630
  function formatDocument(document2, options = {}) {
34562
34631
  const schema = options.schema ?? "auto";
34563
- const useDraft = schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.6" || schema === "2.0-draft" || document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" || document2.version === "2.6" || document2.version === "2.0-draft";
34632
+ const useDraft = schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.6" || schema === "3.0" || schema === "2.0-draft" || document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" || document2.version === "3.0" || document2.version === "2.6" || document2.version === "2.0-draft";
34564
34633
  if (useDraft) {
34565
34634
  if (schema === "2.0-draft") {
34566
34635
  const legacyDraftDocument = document2.version === "2.0-draft" ? document2 : document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" || document2.version === "2.6" ? {
@@ -34570,12 +34639,12 @@ void main() {
34570
34639
  } : upgradeDocumentToDraftV2(document2);
34571
34640
  return formatDraftDocument(legacyDraftDocument);
34572
34641
  }
34573
- const atlasDocument = document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" || document2.version === "2.6" ? document2 : document2.version === "2.0-draft" ? {
34642
+ const atlasDocument = document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" || document2.version === "2.6" || document2.version === "3.0" ? document2 : document2.version === "2.0-draft" ? {
34574
34643
  ...document2,
34575
34644
  version: "2.0",
34576
34645
  schemaVersion: "2.0"
34577
34646
  } : upgradeDocumentToV2(document2);
34578
- if ((schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.6") && atlasDocument.version !== schema) {
34647
+ if ((schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.6" || schema === "3.0") && atlasDocument.version !== schema) {
34579
34648
  return formatAtlasDocument({
34580
34649
  ...atlasDocument,
34581
34650
  version: schema,
@@ -34615,6 +34684,10 @@ void main() {
34615
34684
  lines.push("");
34616
34685
  lines.push(...formatAtlasEvent(event));
34617
34686
  }
34687
+ for (const trajectory of [...document2.trajectories].sort(compareIdLike)) {
34688
+ lines.push("");
34689
+ lines.push(...formatAtlasTrajectory(trajectory));
34690
+ }
34618
34691
  const sortedObjects = [...document2.objects].sort(compareObjects);
34619
34692
  if (sortedObjects.length > 0 && lines.at(-1) !== "") {
34620
34693
  lines.push("");
@@ -34798,6 +34871,9 @@ void main() {
34798
34871
  if (object.groups?.length) {
34799
34872
  lines.push(`groups ${object.groups.join(" ")}`);
34800
34873
  }
34874
+ if (object.trajectoryId) {
34875
+ lines.push(`trajectory ${object.trajectoryId}`);
34876
+ }
34801
34877
  if (object.epoch) {
34802
34878
  lines.push(`epoch ${quoteIfNeeded(object.epoch)}`);
34803
34879
  }
@@ -34958,6 +35034,9 @@ void main() {
34958
35034
  if (event.summary) {
34959
35035
  lines.push(` summary ${quoteIfNeeded(event.summary)}`);
34960
35036
  }
35037
+ if (event.trajectoryId) {
35038
+ lines.push(` trajectory ${event.trajectoryId}`);
35039
+ }
34961
35040
  if (event.targetObjectId) {
34962
35041
  lines.push(` target ${event.targetObjectId}`);
34963
35042
  }
@@ -35000,12 +35079,80 @@ void main() {
35000
35079
  function formatEventPoseFields(pose) {
35001
35080
  return [
35002
35081
  ...formatPlacement(pose.placement),
35082
+ ...pose.trajectorySegmentId ? [`segment ${pose.trajectorySegmentId}`] : [],
35083
+ ...pose.trajectoryManeuverId ? [`maneuver ${pose.trajectoryManeuverId}`] : [],
35003
35084
  ...pose.epoch ? [`epoch ${quoteIfNeeded(pose.epoch)}`] : [],
35004
35085
  ...pose.referencePlane ? [`referencePlane ${quoteIfNeeded(pose.referencePlane)}`] : [],
35005
35086
  ...formatOptionalUnit("inner", pose.inner),
35006
35087
  ...formatOptionalUnit("outer", pose.outer)
35007
35088
  ];
35008
35089
  }
35090
+ function formatAtlasTrajectory(trajectory) {
35091
+ const lines = [`trajectory ${trajectory.id}`];
35092
+ if (trajectory.label) {
35093
+ lines.push(` label ${quoteIfNeeded(trajectory.label)}`);
35094
+ }
35095
+ if (trajectory.summary) {
35096
+ lines.push(` summary ${quoteIfNeeded(trajectory.summary)}`);
35097
+ }
35098
+ if (trajectory.craftObjectId) {
35099
+ lines.push(` craft ${trajectory.craftObjectId}`);
35100
+ }
35101
+ if (trajectory.tags.length > 0) {
35102
+ lines.push(` tags ${trajectory.tags.map(quoteIfNeeded).join(" ")}`);
35103
+ }
35104
+ if (trajectory.color) {
35105
+ lines.push(` color ${quoteIfNeeded(trajectory.color)}`);
35106
+ }
35107
+ if (trajectory.hidden) {
35108
+ lines.push(" hidden true");
35109
+ }
35110
+ for (const segment of [...trajectory.segments].sort(compareIdLike)) {
35111
+ lines.push("");
35112
+ lines.push(` segment ${segment.id}`);
35113
+ for (const field of formatTrajectorySegmentFields(segment)) {
35114
+ lines.push(` ${field}`);
35115
+ }
35116
+ for (const maneuver of [...segment.maneuvers].sort(compareIdLike)) {
35117
+ lines.push(` maneuver ${maneuver.id}`);
35118
+ for (const field of formatTrajectoryManeuverFields(maneuver)) {
35119
+ lines.push(` ${field}`);
35120
+ }
35121
+ }
35122
+ }
35123
+ return lines;
35124
+ }
35125
+ function formatTrajectorySegmentFields(segment) {
35126
+ return [
35127
+ `kind ${segment.kind}`,
35128
+ ...segment.label ? [`label ${quoteIfNeeded(segment.label)}`] : [],
35129
+ ...segment.summary ? [`summary ${quoteIfNeeded(segment.summary)}`] : [],
35130
+ ...segment.fromObjectId ? [`from ${segment.fromObjectId}`] : [],
35131
+ ...segment.toObjectId ? [`to ${segment.toObjectId}`] : [],
35132
+ ...segment.aroundObjectId ? [`around ${segment.aroundObjectId}`] : [],
35133
+ ...segment.assist?.objectId ? [`assist ${segment.assist.objectId}`] : [],
35134
+ ...segment.epoch ? [`epoch ${quoteIfNeeded(segment.epoch)}`] : [],
35135
+ ...formatOptionalUnit("periapsis", segment.periapsis),
35136
+ ...formatOptionalUnit("apoapsis", segment.apoapsis),
35137
+ ...formatOptionalUnit("inclination", segment.inclination),
35138
+ ...formatOptionalUnit("duration", segment.duration),
35139
+ ...formatOptionalUnit("deltaV", segment.deltaV),
35140
+ ...formatOptionalUnit("phaseAngle", segment.phaseAngle),
35141
+ ...formatOptionalUnit("turnAngle", segment.turnAngle),
35142
+ ...formatOptionalUnit("energy", segment.energy),
35143
+ ...segment.notes.length > 0 ? [`notes ${segment.notes.map(quoteIfNeeded).join(" ")}`] : []
35144
+ ];
35145
+ }
35146
+ function formatTrajectoryManeuverFields(maneuver) {
35147
+ return [
35148
+ `kind ${quoteIfNeeded(maneuver.kind)}`,
35149
+ ...maneuver.label ? [`label ${quoteIfNeeded(maneuver.label)}`] : [],
35150
+ ...maneuver.epoch ? [`epoch ${quoteIfNeeded(maneuver.epoch)}`] : [],
35151
+ ...formatOptionalUnit("deltaV", maneuver.deltaV),
35152
+ ...formatOptionalUnit("duration", maneuver.duration),
35153
+ ...maneuver.notes.length > 0 ? [`notes ${maneuver.notes.map(quoteIfNeeded).join(" ")}`] : []
35154
+ ];
35155
+ }
35009
35156
  function hasCameraValues(camera) {
35010
35157
  return camera.azimuth !== null || camera.elevation !== null || camera.roll !== null || camera.distance !== null;
35011
35158
  }
@@ -35097,10 +35244,12 @@ void main() {
35097
35244
  return 5;
35098
35245
  case "ring":
35099
35246
  return 6;
35100
- case "structure":
35247
+ case "craft":
35101
35248
  return 7;
35102
- case "phenomenon":
35249
+ case "structure":
35103
35250
  return 8;
35251
+ case "phenomenon":
35252
+ return 9;
35104
35253
  }
35105
35254
  }
35106
35255
  function quoteIfNeeded(value) {
@@ -35277,6 +35426,7 @@ void main() {
35277
35426
  const objectMap = new Map(document2.objects.map((object) => [object.id, object]));
35278
35427
  const groupIds = new Set(document2.groups.map((group) => group.id));
35279
35428
  const eventIds = new Set(document2.events.map((event) => event.id));
35429
+ const trajectoryMap = new Map(document2.trajectories.map((trajectory) => [trajectory.id, trajectory]));
35280
35430
  if (!document2.system) {
35281
35431
  diagnostics.push(error("validate.system.required", "Atlas documents must declare exactly one system."));
35282
35432
  }
@@ -35287,6 +35437,7 @@ void main() {
35287
35437
  ["annotation", document2.system?.annotations.map((annotation) => annotation.id) ?? []],
35288
35438
  ["relation", document2.relations.map((relation) => relation.id)],
35289
35439
  ["event", document2.events.map((event) => event.id)],
35440
+ ["trajectory", document2.trajectories.map((trajectory) => trajectory.id)],
35290
35441
  ["object", document2.objects.map((object) => object.id)]
35291
35442
  ]) {
35292
35443
  for (const id of ids) {
@@ -35305,10 +35456,13 @@ void main() {
35305
35456
  validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap);
35306
35457
  }
35307
35458
  for (const object of document2.objects) {
35308
- validateObject(object, document2.system, objectMap, groupIds, diagnostics);
35459
+ validateObject(object, document2.system, objectMap, groupIds, trajectoryMap, diagnostics);
35309
35460
  }
35310
35461
  for (const event of document2.events) {
35311
- validateEvent(event, document2.system, objectMap, diagnostics);
35462
+ validateEvent(event, document2.system, objectMap, trajectoryMap, diagnostics);
35463
+ }
35464
+ for (const trajectory of document2.trajectories) {
35465
+ validateTrajectory(trajectory, objectMap, diagnostics);
35312
35466
  }
35313
35467
  return diagnostics;
35314
35468
  }
@@ -35346,7 +35500,7 @@ void main() {
35346
35500
  validateProjection(viewpoint.projection, diagnostics, `viewpoint.${viewpoint.id}.projection`, viewpoint.id);
35347
35501
  validateCamera(viewpoint.camera, viewpoint.projection, viewpoint.rotationDeg, diagnostics, viewpoint.id, viewpoint.focusObjectId, viewpoint.selectedObjectId, filter, objectMap);
35348
35502
  }
35349
- function validateObject(object, system, objectMap, groupIds, diagnostics) {
35503
+ function validateObject(object, system, objectMap, groupIds, trajectoryMap, diagnostics) {
35350
35504
  const placement = object.placement;
35351
35505
  const orbitPlacement = placement?.mode === "orbit" ? placement : null;
35352
35506
  const parentObject = placement?.mode === "orbit" ? objectMap.get(placement.target) ?? null : null;
@@ -35363,6 +35517,13 @@ void main() {
35363
35517
  if (typeof object.referencePlane === "string" && !object.referencePlane.trim()) {
35364
35518
  diagnostics.push(warn("validate.referencePlane.empty", `Object "${object.id}" defines an empty reference plane string.`, object.id, "referencePlane"));
35365
35519
  }
35520
+ if (object.trajectoryId) {
35521
+ if (!trajectoryMap.has(object.trajectoryId)) {
35522
+ diagnostics.push(error("validate.trajectory.object.unknown", `Unknown trajectory "${object.trajectoryId}" on "${object.id}".`, object.id, "trajectory"));
35523
+ } else if (!isTrajectoryCapableObject(object)) {
35524
+ diagnostics.push(error("validate.trajectory.object.invalidType", `Only craft or legacy ship-like structures may reference trajectories; found "${object.type}" on "${object.id}".`, object.id, "trajectory"));
35525
+ }
35526
+ }
35366
35527
  if (orbitPlacement) {
35367
35528
  if (!objectMap.has(orbitPlacement.target)) {
35368
35529
  diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
@@ -35389,8 +35550,8 @@ void main() {
35389
35550
  }
35390
35551
  }
35391
35552
  if (placement?.mode === "at") {
35392
- if (object.type !== "structure" && object.type !== "phenomenon") {
35393
- diagnostics.push(error("validate.at.objectType", `Only structures and phenomena may use "at" placement; found "${object.type}" on "${object.id}".`, object.id, "at"));
35553
+ if (object.type !== "craft" && object.type !== "structure" && object.type !== "phenomenon") {
35554
+ diagnostics.push(error("validate.at.objectType", `Only craft, structures, and phenomena may use "at" placement; found "${object.type}" on "${object.id}".`, object.id, "at"));
35394
35555
  }
35395
35556
  if (!validateAtTarget(object, objectMap, diagnostics)) {
35396
35557
  diagnostics.push(error("validate.at.target.unknown", `Unknown at-reference target "${placement.target}" on "${object.id}".`, object.id, "at"));
@@ -35434,7 +35595,7 @@ void main() {
35434
35595
  }
35435
35596
  }
35436
35597
  }
35437
- function validateEvent(event, system, objectMap, diagnostics) {
35598
+ function validateEvent(event, system, objectMap, trajectoryMap, diagnostics) {
35438
35599
  const fieldPrefix = `event.${event.id}`;
35439
35600
  const referencedIds = /* @__PURE__ */ new Set();
35440
35601
  if (!event.kind.trim()) {
@@ -35446,6 +35607,9 @@ void main() {
35446
35607
  if (typeof event.referencePlane === "string" && !event.referencePlane.trim()) {
35447
35608
  diagnostics.push(warn("validate.event.referencePlane.empty", `Event "${event.id}" defines an empty reference plane string.`, void 0, `${fieldPrefix}.referencePlane`));
35448
35609
  }
35610
+ if (event.trajectoryId && !trajectoryMap.has(event.trajectoryId)) {
35611
+ diagnostics.push(error("validate.event.trajectory.unknown", `Unknown trajectory "${event.trajectoryId}" on event "${event.id}".`, void 0, `${fieldPrefix}.trajectory`));
35612
+ }
35449
35613
  if (!event.targetObjectId && event.participantObjectIds.length === 0) {
35450
35614
  diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
35451
35615
  }
@@ -35492,19 +35656,25 @@ void main() {
35492
35656
  if (!referencedIds.has(pose.objectId)) {
35493
35657
  diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
35494
35658
  }
35495
- validateEventPose(pose, object, event, system, objectMap, diagnostics, poseFieldPrefix, event.id);
35659
+ validateEventPose(pose, object, event, system, objectMap, trajectoryMap, diagnostics, poseFieldPrefix, event.id);
35496
35660
  }
35497
35661
  const missingPoseIds = [...referencedIds].filter((objectId) => !poseIds.has(objectId));
35498
35662
  if (event.positions.length > 0 && missingPoseIds.length > 0) {
35499
35663
  diagnostics.push(warn("validate.event.positions.partial", `Event "${event.id}" leaves ${missingPoseIds.length} referenced object(s) on their base placement.`, void 0, `${fieldPrefix}.positions`));
35500
35664
  }
35501
35665
  }
35502
- function validateEventPose(pose, object, event, system, objectMap, diagnostics, fieldPrefix, eventId) {
35666
+ function validateEventPose(pose, object, event, system, objectMap, trajectoryMap, diagnostics, fieldPrefix, eventId) {
35503
35667
  const placement = pose.placement;
35504
35668
  if (!placement) {
35505
35669
  diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
35506
35670
  return;
35507
35671
  }
35672
+ if (pose.trajectorySegmentId && !findTrajectorySegment(trajectoryMap, pose.trajectorySegmentId)) {
35673
+ diagnostics.push(error("validate.event.pose.segment.unknown", `Unknown trajectory segment "${pose.trajectorySegmentId}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.segment`));
35674
+ }
35675
+ if (pose.trajectoryManeuverId && !findTrajectoryManeuver(trajectoryMap, pose.trajectoryManeuverId)) {
35676
+ diagnostics.push(error("validate.event.pose.maneuver.unknown", `Unknown trajectory maneuver "${pose.trajectoryManeuverId}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.maneuver`));
35677
+ }
35508
35678
  if (placement.mode === "orbit") {
35509
35679
  if (!objectMap.has(placement.target)) {
35510
35680
  diagnostics.push(error("validate.event.pose.orbit.target.unknown", `Unknown event orbit target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.orbit`));
@@ -35533,8 +35703,8 @@ void main() {
35533
35703
  return;
35534
35704
  }
35535
35705
  if (placement.mode === "at") {
35536
- if (object.type !== "structure" && object.type !== "phenomenon") {
35537
- diagnostics.push(error("validate.event.pose.at.objectType", `Only structures and phenomena may use "at" placement in events; found "${object.type}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
35706
+ if (object.type !== "craft" && object.type !== "structure" && object.type !== "phenomenon") {
35707
+ diagnostics.push(error("validate.event.pose.at.objectType", `Only craft, structures, and phenomena may use "at" placement in events; found "${object.type}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
35538
35708
  }
35539
35709
  const reference = placement.reference;
35540
35710
  if (reference.kind === "named" && !objectMap.has(reference.name)) {
@@ -35550,6 +35720,78 @@ void main() {
35550
35720
  }
35551
35721
  }
35552
35722
  }
35723
+ function validateTrajectory(trajectory, objectMap, diagnostics) {
35724
+ if (trajectory.craftObjectId) {
35725
+ const craft = objectMap.get(trajectory.craftObjectId);
35726
+ if (!craft) {
35727
+ diagnostics.push(error("validate.trajectory.craft.unknown", `Unknown craft "${trajectory.craftObjectId}" on trajectory "${trajectory.id}".`, void 0, `trajectory.${trajectory.id}.craft`));
35728
+ } else if (!isTrajectoryCapableObject(craft)) {
35729
+ diagnostics.push(error("validate.trajectory.craft.invalidType", `Trajectory "${trajectory.id}" targets "${trajectory.craftObjectId}", which is not craft-like.`, void 0, `trajectory.${trajectory.id}.craft`));
35730
+ }
35731
+ }
35732
+ for (const segment of trajectory.segments) {
35733
+ validateTrajectorySegment(trajectory.id, segment, objectMap, diagnostics);
35734
+ }
35735
+ }
35736
+ function validateTrajectorySegment(trajectoryId, segment, objectMap, diagnostics) {
35737
+ const fieldPrefix = `trajectory.${trajectoryId}.segment.${segment.id}`;
35738
+ for (const [field, objectId] of [
35739
+ ["from", segment.fromObjectId],
35740
+ ["to", segment.toObjectId],
35741
+ ["around", segment.aroundObjectId]
35742
+ ]) {
35743
+ if (objectId && !objectMap.has(objectId)) {
35744
+ diagnostics.push(error(`validate.trajectory.segment.${field}.unknown`, `Unknown ${field} object "${objectId}" on trajectory "${trajectoryId}" segment "${segment.id}".`, void 0, `${fieldPrefix}.${field}`));
35745
+ }
35746
+ }
35747
+ if (segment.assist?.objectId && !objectMap.has(segment.assist.objectId)) {
35748
+ diagnostics.push(error("validate.trajectory.segment.assist.unknown", `Unknown assist object "${segment.assist.objectId}" on trajectory "${trajectoryId}" segment "${segment.id}".`, void 0, `${fieldPrefix}.assist`));
35749
+ }
35750
+ if (segment.kind === "flyby" && !segment.assist?.objectId) {
35751
+ diagnostics.push(error("validate.trajectory.segment.assist.required", `Trajectory "${trajectoryId}" segment "${segment.id}" is a flyby and requires an "assist" object.`, void 0, `${fieldPrefix}.assist`));
35752
+ }
35753
+ if ((segment.kind === "capture" || segment.kind === "departure") && !segment.toObjectId && !segment.aroundObjectId) {
35754
+ diagnostics.push(error("validate.trajectory.segment.target.required", `Trajectory "${trajectoryId}" segment "${segment.id}" requires a target reference.`, void 0, `${fieldPrefix}.to`));
35755
+ }
35756
+ for (const maneuver of segment.maneuvers) {
35757
+ validateTrajectoryManeuver(trajectoryId, segment.id, maneuver, diagnostics);
35758
+ }
35759
+ }
35760
+ function validateTrajectoryManeuver(trajectoryId, segmentId, maneuver, diagnostics) {
35761
+ if (!maneuver.kind.trim()) {
35762
+ diagnostics.push(error("validate.trajectory.maneuver.kind.required", `Trajectory "${trajectoryId}" segment "${segmentId}" maneuver "${maneuver.id}" is missing a kind.`, void 0, `trajectory.${trajectoryId}.segment.${segmentId}.maneuver.${maneuver.id}.kind`));
35763
+ }
35764
+ }
35765
+ function isTrajectoryCapableObject(object) {
35766
+ if (object.type === "craft") {
35767
+ return true;
35768
+ }
35769
+ if (object.type !== "structure") {
35770
+ return false;
35771
+ }
35772
+ const kind = typeof object.properties.kind === "string" ? object.properties.kind.toLowerCase() : "";
35773
+ return kind === "ship" || kind === "probe" || kind === "station";
35774
+ }
35775
+ function findTrajectorySegment(trajectories, segmentId) {
35776
+ for (const trajectory of trajectories.values()) {
35777
+ const match = trajectory.segments.find((segment) => segment.id === segmentId);
35778
+ if (match) {
35779
+ return match;
35780
+ }
35781
+ }
35782
+ return null;
35783
+ }
35784
+ function findTrajectoryManeuver(trajectories, maneuverId) {
35785
+ for (const trajectory of trajectories.values()) {
35786
+ for (const segment of trajectory.segments) {
35787
+ const match = segment.maneuvers.find((maneuver) => maneuver.id === maneuverId);
35788
+ if (match) {
35789
+ return match;
35790
+ }
35791
+ }
35792
+ }
35793
+ return null;
35794
+ }
35553
35795
  function validateAtTarget(object, objectMap, diagnostics) {
35554
35796
  const reference = object.placement?.mode === "at" ? object.placement.reference : null;
35555
35797
  if (!reference) {
@@ -35730,6 +35972,14 @@ void main() {
35730
35972
  "habitability",
35731
35973
  "settlement"
35732
35974
  ]);
35975
+ var TRAJECTORY_SEGMENT_KINDS = /* @__PURE__ */ new Set([
35976
+ "departure",
35977
+ "transfer",
35978
+ "flyby",
35979
+ "capture",
35980
+ "stationkeeping",
35981
+ "escape"
35982
+ ]);
35733
35983
  var DRAFT_OBJECT_FIELD_SPECS = /* @__PURE__ */ new Map();
35734
35984
  for (const key of [
35735
35985
  "orbit",
@@ -35761,7 +36011,8 @@ void main() {
35761
36011
  "outer",
35762
36012
  "on",
35763
36013
  "source",
35764
- "cycle"
36014
+ "cycle",
36015
+ "trajectory"
35765
36016
  ]) {
35766
36017
  const schema = getFieldSchema(key);
35767
36018
  if (schema) {
@@ -35786,11 +36037,12 @@ void main() {
35786
36037
  { key: "derive", inlineMode: "pair", allowRepeat: true },
35787
36038
  { key: "validate", inlineMode: "single", allowRepeat: true },
35788
36039
  { key: "locked", inlineMode: "multiple", allowRepeat: false },
35789
- { key: "tolerance", inlineMode: "pair", allowRepeat: true }
36040
+ { key: "tolerance", inlineMode: "pair", allowRepeat: true },
36041
+ { key: "trajectory", inlineMode: "single", allowRepeat: false }
35790
36042
  ]) {
35791
36043
  DRAFT_OBJECT_FIELD_SPECS.set(spec.key, {
35792
36044
  key: spec.key,
35793
- version: "2.1",
36045
+ version: spec.key === "trajectory" ? "3.0" : "2.1",
35794
36046
  inlineMode: spec.inlineMode,
35795
36047
  allowRepeat: spec.allowRepeat
35796
36048
  });
@@ -35811,7 +36063,9 @@ void main() {
35811
36063
  "inner",
35812
36064
  "outer",
35813
36065
  "epoch",
35814
- "referencePlane"
36066
+ "referencePlane",
36067
+ "segment",
36068
+ "maneuver"
35815
36069
  ]);
35816
36070
  function parseWorldOrbitAtlas(source) {
35817
36071
  return parseAtlasSource(source);
@@ -35831,6 +36085,7 @@ void main() {
35831
36085
  const groups = [];
35832
36086
  const relations = [];
35833
36087
  const events = [];
36088
+ const trajectories = [];
35834
36089
  const eventPoseNodes = /* @__PURE__ */ new Map();
35835
36090
  let sawDefaults = false;
35836
36091
  let sawAtlas = false;
@@ -35839,6 +36094,7 @@ void main() {
35839
36094
  const groupIds = /* @__PURE__ */ new Set();
35840
36095
  const relationIds = /* @__PURE__ */ new Set();
35841
36096
  const eventIds = /* @__PURE__ */ new Set();
36097
+ const trajectoryIds = /* @__PURE__ */ new Set();
35842
36098
  for (let index = 0; index < lines.length; index++) {
35843
36099
  const rawLine = lines[index];
35844
36100
  const lineNumber = index + 1;
@@ -35869,7 +36125,7 @@ void main() {
35869
36125
  continue;
35870
36126
  }
35871
36127
  if (indent === 0) {
35872
- section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, { sawDefaults, sawAtlas });
36128
+ section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, trajectories, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, trajectoryIds, { sawDefaults, sawAtlas });
35873
36129
  if (section.kind === "system") {
35874
36130
  system = section.system;
35875
36131
  } else if (section.kind === "defaults") {
@@ -35885,7 +36141,7 @@ void main() {
35885
36141
  handleSectionLine(section, indent, tokens, lineNumber);
35886
36142
  }
35887
36143
  if (!sawSchemaHeader) {
35888
- throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
36144
+ throw new WorldOrbitError('Missing required atlas schema header "schema 2.0" or "schema 3.0"');
35889
36145
  }
35890
36146
  const objects = objectNodes.map((node) => normalizeDraftObject(node, sourceSchemaVersion, diagnostics));
35891
36147
  const normalizedEvents = events.map((event) => normalizeDraftEvent(event, eventPoseNodes.get(event.id) ?? []));
@@ -35898,6 +36154,7 @@ void main() {
35898
36154
  groups,
35899
36155
  relations,
35900
36156
  events: normalizedEvents,
36157
+ trajectories,
35901
36158
  objects,
35902
36159
  diagnostics
35903
36160
  };
@@ -35927,13 +36184,13 @@ void main() {
35927
36184
  return document2;
35928
36185
  }
35929
36186
  function assertDraftSchemaHeader(tokens, line) {
35930
- if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1", "2.5", "2.6"].includes(tokens[1].value.toLowerCase())) {
35931
- throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", "schema 2.5", "schema 2.6", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
36187
+ if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1", "2.5", "2.6", "3.0"].includes(tokens[1].value.toLowerCase())) {
36188
+ throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", "schema 2.5", "schema 2.6", "schema 3.0", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
35932
36189
  }
35933
36190
  const version = tokens[1].value.toLowerCase();
35934
- return version === "2.6" ? "2.6" : version === "2.5" ? "2.5" : version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
36191
+ return version === "2.6" ? "2.6" : version === "3.0" ? "3.0" : version === "2.5" ? "2.5" : version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
35935
36192
  }
35936
- function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
36193
+ function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, trajectories, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, trajectoryIds, flags) {
35937
36194
  const keyword = tokens[0]?.value.toLowerCase();
35938
36195
  switch (keyword) {
35939
36196
  case "system":
@@ -35987,6 +36244,9 @@ void main() {
35987
36244
  case "event":
35988
36245
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "event", { line, column: tokens[0].column });
35989
36246
  return startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics);
36247
+ case "trajectory":
36248
+ warnIfSchema30Feature(sourceSchemaVersion, diagnostics, "trajectory", { line, column: tokens[0].column });
36249
+ return startTrajectorySection(tokens, line, trajectories, trajectoryIds, sourceSchemaVersion, diagnostics);
35990
36250
  case "object":
35991
36251
  return startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes);
35992
36252
  default:
@@ -36194,6 +36454,45 @@ void main() {
36194
36454
  activePoseSeenFields: /* @__PURE__ */ new Set()
36195
36455
  };
36196
36456
  }
36457
+ function startTrajectorySection(tokens, line, trajectories, trajectoryIds, sourceSchemaVersion, diagnostics) {
36458
+ if (tokens.length !== 2) {
36459
+ throw new WorldOrbitError("Invalid trajectory declaration", line, tokens[0]?.column ?? 1);
36460
+ }
36461
+ const id = normalizeIdentifier2(tokens[1].value);
36462
+ if (!id) {
36463
+ throw new WorldOrbitError("Trajectory id must not be empty", line, tokens[1].column);
36464
+ }
36465
+ if (trajectoryIds.has(id)) {
36466
+ throw new WorldOrbitError(`Duplicate trajectory id "${id}"`, line, tokens[1].column);
36467
+ }
36468
+ const trajectory = {
36469
+ id,
36470
+ label: humanizeIdentifier3(id),
36471
+ summary: null,
36472
+ craftObjectId: null,
36473
+ tags: [],
36474
+ color: null,
36475
+ hidden: false,
36476
+ segments: []
36477
+ };
36478
+ trajectories.push(trajectory);
36479
+ trajectoryIds.add(id);
36480
+ return {
36481
+ kind: "trajectory",
36482
+ trajectory,
36483
+ sourceSchemaVersion,
36484
+ diagnostics,
36485
+ seenFields: /* @__PURE__ */ new Set(),
36486
+ inSegment: false,
36487
+ segmentIndent: null,
36488
+ activeSegment: null,
36489
+ activeSegmentSeenFields: /* @__PURE__ */ new Set(),
36490
+ inManeuver: false,
36491
+ maneuverIndent: null,
36492
+ activeManeuver: null,
36493
+ activeManeuverSeenFields: /* @__PURE__ */ new Set()
36494
+ };
36495
+ }
36197
36496
  function startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes) {
36198
36497
  if (tokens.length < 3) {
36199
36498
  throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
@@ -36253,6 +36552,9 @@ void main() {
36253
36552
  case "event":
36254
36553
  applyEventField(section, indent, tokens, line);
36255
36554
  return;
36555
+ case "trajectory":
36556
+ applyTrajectoryField(section, indent, tokens, line);
36557
+ return;
36256
36558
  case "object":
36257
36559
  applyObjectField(section, indent, tokens, line);
36258
36560
  return;
@@ -36571,6 +36873,12 @@ void main() {
36571
36873
  column: tokens[0]?.column ?? 1
36572
36874
  });
36573
36875
  }
36876
+ if (tokens[0]?.value === "segment" || tokens[0]?.value === "maneuver") {
36877
+ warnIfSchema30Feature(section.sourceSchemaVersion, section.diagnostics, `pose.${tokens[0].value}`, {
36878
+ line,
36879
+ column: tokens[0]?.column ?? 1
36880
+ });
36881
+ }
36574
36882
  section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
36575
36883
  return;
36576
36884
  }
@@ -36613,6 +36921,13 @@ void main() {
36613
36921
  case "summary":
36614
36922
  section.event.summary = joinFieldValue(tokens, line);
36615
36923
  return;
36924
+ case "trajectory":
36925
+ warnIfSchema30Feature(section.sourceSchemaVersion, section.diagnostics, "event.trajectory", {
36926
+ line,
36927
+ column: tokens[0].column
36928
+ });
36929
+ section.event.trajectoryId = joinFieldValue(tokens, line);
36930
+ return;
36616
36931
  case "target":
36617
36932
  section.event.targetObjectId = joinFieldValue(tokens, line);
36618
36933
  return;
@@ -36655,6 +36970,219 @@ void main() {
36655
36970
  throw new WorldOrbitError(`Unknown event field "${tokens[0].value}"`, line, tokens[0].column);
36656
36971
  }
36657
36972
  }
36973
+ function applyTrajectoryField(section, indent, tokens, line) {
36974
+ if (section.activeManeuver && indent <= (section.maneuverIndent ?? 0)) {
36975
+ section.activeManeuver = null;
36976
+ section.maneuverIndent = null;
36977
+ section.activeManeuverSeenFields.clear();
36978
+ section.inManeuver = false;
36979
+ }
36980
+ if (section.activeSegment && indent <= (section.segmentIndent ?? 0)) {
36981
+ section.activeSegment = null;
36982
+ section.segmentIndent = null;
36983
+ section.activeSegmentSeenFields.clear();
36984
+ section.inSegment = false;
36985
+ }
36986
+ if (section.activeManeuver) {
36987
+ applyTrajectoryManeuverField(section, tokens, line);
36988
+ return;
36989
+ }
36990
+ if (section.activeSegment) {
36991
+ if (tokens[0]?.value.toLowerCase() === "maneuver") {
36992
+ if (tokens.length !== 2) {
36993
+ throw new WorldOrbitError("Invalid trajectory maneuver declaration", line, tokens[0]?.column ?? 1);
36994
+ }
36995
+ const id = normalizeIdentifier2(tokens[1].value);
36996
+ if (!id) {
36997
+ throw new WorldOrbitError("Trajectory maneuver id must not be empty", line, tokens[1].column);
36998
+ }
36999
+ if (section.activeSegment.maneuvers.some((maneuver2) => maneuver2.id === id)) {
37000
+ throw new WorldOrbitError(`Duplicate trajectory maneuver id "${id}"`, line, tokens[1].column);
37001
+ }
37002
+ const maneuver = {
37003
+ id,
37004
+ kind: "burn",
37005
+ label: null,
37006
+ epoch: null,
37007
+ notes: []
37008
+ };
37009
+ section.activeSegment.maneuvers.push(maneuver);
37010
+ section.activeManeuver = maneuver;
37011
+ section.inManeuver = true;
37012
+ section.maneuverIndent = indent;
37013
+ section.activeManeuverSeenFields = /* @__PURE__ */ new Set();
37014
+ return;
37015
+ }
37016
+ applyTrajectorySegmentField(section, tokens, line);
37017
+ return;
37018
+ }
37019
+ if (tokens[0]?.value.toLowerCase() === "segment") {
37020
+ if (tokens.length !== 2) {
37021
+ throw new WorldOrbitError("Invalid trajectory segment declaration", line, tokens[0]?.column ?? 1);
37022
+ }
37023
+ const id = normalizeIdentifier2(tokens[1].value);
37024
+ if (!id) {
37025
+ throw new WorldOrbitError("Trajectory segment id must not be empty", line, tokens[1].column);
37026
+ }
37027
+ if (section.trajectory.segments.some((segment2) => segment2.id === id)) {
37028
+ throw new WorldOrbitError(`Duplicate trajectory segment id "${id}"`, line, tokens[1].column);
37029
+ }
37030
+ const segment = {
37031
+ id,
37032
+ kind: "transfer",
37033
+ label: null,
37034
+ summary: null,
37035
+ fromObjectId: null,
37036
+ toObjectId: null,
37037
+ aroundObjectId: null,
37038
+ assist: null,
37039
+ epoch: null,
37040
+ notes: [],
37041
+ maneuvers: []
37042
+ };
37043
+ section.trajectory.segments.push(segment);
37044
+ section.activeSegment = {
37045
+ id,
37046
+ fields: [],
37047
+ maneuvers: segment.maneuvers,
37048
+ assist: null,
37049
+ location: { line, column: tokens[0].column }
37050
+ };
37051
+ section.inSegment = true;
37052
+ section.segmentIndent = indent;
37053
+ section.activeSegmentSeenFields = /* @__PURE__ */ new Set();
37054
+ return;
37055
+ }
37056
+ const key = requireUniqueField(tokens, section.seenFields, line);
37057
+ const value = joinFieldValue(tokens, line);
37058
+ switch (key) {
37059
+ case "label":
37060
+ section.trajectory.label = value;
37061
+ return;
37062
+ case "summary":
37063
+ section.trajectory.summary = value;
37064
+ return;
37065
+ case "craft":
37066
+ section.trajectory.craftObjectId = value;
37067
+ return;
37068
+ case "tags":
37069
+ section.trajectory.tags = parseTokenList(tokens.slice(1), line, "tags");
37070
+ return;
37071
+ case "color":
37072
+ section.trajectory.color = value;
37073
+ return;
37074
+ case "hidden":
37075
+ section.trajectory.hidden = parseAtlasBoolean(value, "hidden", {
37076
+ line,
37077
+ column: tokens[0].column
37078
+ });
37079
+ return;
37080
+ default:
37081
+ throw new WorldOrbitError(`Unknown trajectory field "${tokens[0].value}"`, line, tokens[0].column);
37082
+ }
37083
+ }
37084
+ function applyTrajectorySegmentField(section, tokens, line) {
37085
+ const segment = section.activeSegment;
37086
+ if (!segment) {
37087
+ return;
37088
+ }
37089
+ const key = requireUniqueField(tokens, section.activeSegmentSeenFields, line);
37090
+ const value = joinFieldValue(tokens, line);
37091
+ const target = section.trajectory.segments.find((entry) => entry.id === segment.id);
37092
+ switch (key) {
37093
+ case "kind": {
37094
+ const normalized = value.toLowerCase();
37095
+ if (!TRAJECTORY_SEGMENT_KINDS.has(normalized)) {
37096
+ throw new WorldOrbitError(`Unknown trajectory segment kind "${value}"`, line, tokens[0].column);
37097
+ }
37098
+ target.kind = normalized;
37099
+ return;
37100
+ }
37101
+ case "label":
37102
+ target.label = value;
37103
+ return;
37104
+ case "summary":
37105
+ target.summary = value;
37106
+ return;
37107
+ case "from":
37108
+ target.fromObjectId = value;
37109
+ return;
37110
+ case "to":
37111
+ target.toObjectId = value;
37112
+ return;
37113
+ case "around":
37114
+ target.aroundObjectId = value;
37115
+ return;
37116
+ case "assist":
37117
+ target.assist = {
37118
+ objectId: value,
37119
+ notes: []
37120
+ };
37121
+ return;
37122
+ case "epoch":
37123
+ target.epoch = value;
37124
+ return;
37125
+ case "periapsis":
37126
+ target.periapsis = parseAtlasUnitValue(value, { line, column: tokens[0].column }, "periapsis");
37127
+ return;
37128
+ case "apoapsis":
37129
+ target.apoapsis = parseAtlasUnitValue(value, { line, column: tokens[0].column }, "apoapsis");
37130
+ return;
37131
+ case "inclination":
37132
+ target.inclination = parseAtlasUnitValue(value, { line, column: tokens[0].column }, "inclination");
37133
+ return;
37134
+ case "duration":
37135
+ target.duration = parseAtlasUnitValue(value, { line, column: tokens[0].column }, "duration");
37136
+ return;
37137
+ case "deltav":
37138
+ target.deltaV = parseAtlasUnitValue(value, { line, column: tokens[0].column });
37139
+ return;
37140
+ case "phaseangle":
37141
+ target.phaseAngle = parseAtlasUnitValue(value, { line, column: tokens[0].column }, "phaseAngle");
37142
+ return;
37143
+ case "turnangle":
37144
+ target.turnAngle = parseAtlasUnitValue(value, { line, column: tokens[0].column }, "turnAngle");
37145
+ return;
37146
+ case "energy":
37147
+ target.energy = parseAtlasUnitValue(value, { line, column: tokens[0].column });
37148
+ return;
37149
+ case "notes":
37150
+ target.notes = parseTokenList(tokens.slice(1), line, "notes");
37151
+ return;
37152
+ default:
37153
+ throw new WorldOrbitError(`Unknown trajectory segment field "${tokens[0].value}"`, line, tokens[0].column);
37154
+ }
37155
+ }
37156
+ function applyTrajectoryManeuverField(section, tokens, line) {
37157
+ const maneuver = section.activeManeuver;
37158
+ if (!maneuver) {
37159
+ return;
37160
+ }
37161
+ const key = requireUniqueField(tokens, section.activeManeuverSeenFields, line);
37162
+ const value = joinFieldValue(tokens, line);
37163
+ switch (key) {
37164
+ case "kind":
37165
+ maneuver.kind = value;
37166
+ return;
37167
+ case "label":
37168
+ maneuver.label = value;
37169
+ return;
37170
+ case "epoch":
37171
+ maneuver.epoch = value;
37172
+ return;
37173
+ case "deltav":
37174
+ maneuver.deltaV = parseAtlasUnitValue(value, { line, column: tokens[0].column });
37175
+ return;
37176
+ case "duration":
37177
+ maneuver.duration = parseAtlasUnitValue(value, { line, column: tokens[0].column }, "duration");
37178
+ return;
37179
+ case "notes":
37180
+ maneuver.notes = parseTokenList(tokens.slice(1), line, "notes");
37181
+ return;
37182
+ default:
37183
+ throw new WorldOrbitError(`Unknown trajectory maneuver field "${tokens[0].value}"`, line, tokens[0].column);
37184
+ }
37185
+ }
36658
37186
  function parseEventPoseField(tokens, line, seenFields) {
36659
37187
  if (tokens.length < 2) {
36660
37188
  throw new WorldOrbitError("Invalid event pose field line", line, tokens[0]?.column ?? 1);
@@ -36860,6 +37388,11 @@ void main() {
36860
37388
  line,
36861
37389
  column: keyToken.column
36862
37390
  });
37391
+ } else if (spec.version === "3.0") {
37392
+ warnIfSchema30Feature(sourceSchemaVersion, diagnostics, keyToken.value, {
37393
+ line,
37394
+ column: keyToken.column
37395
+ });
36863
37396
  }
36864
37397
  index++;
36865
37398
  const valueTokens = [];
@@ -36910,6 +37443,11 @@ void main() {
36910
37443
  line,
36911
37444
  column: tokens[0].column
36912
37445
  });
37446
+ } else if (spec.version === "3.0") {
37447
+ warnIfSchema30Feature(sourceSchemaVersion, diagnostics, tokens[0].value, {
37448
+ line,
37449
+ column: tokens[0].column
37450
+ });
36913
37451
  }
36914
37452
  const field = {
36915
37453
  type: "field",
@@ -36949,6 +37487,7 @@ void main() {
36949
37487
  const tolerances = fieldMap.get("tolerance")?.map((field) => parseToleranceField(field));
36950
37488
  const typedBlocks = normalizeTypedBlocks(node.typedBlockEntries);
36951
37489
  const info2 = normalizeInfoEntries(node.infoEntries, "info");
37490
+ const trajectoryId = parseOptionalJoinedValue(fieldMap.get("trajectory")?.[0]);
36952
37491
  const object = {
36953
37492
  type: node.objectType,
36954
37493
  id: node.id,
@@ -36978,11 +37517,16 @@ void main() {
36978
37517
  object.tolerances = tolerances;
36979
37518
  if (typedBlocks && Object.keys(typedBlocks).length > 0)
36980
37519
  object.typedBlocks = typedBlocks;
37520
+ if (trajectoryId)
37521
+ object.trajectoryId = trajectoryId;
36981
37522
  if (isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
36982
- if (object.groups || object.epoch || object.referencePlane || object.tidalLock !== void 0 || object.resonance || object.renderHints || object.deriveRules?.length || object.validationRules?.length || object.lockedFields?.length || object.tolerances?.length || object.typedBlocks) {
37523
+ if (object.groups || object.epoch || object.referencePlane || object.tidalLock !== void 0 || object.resonance || object.renderHints || object.deriveRules?.length || object.validationRules?.length || object.lockedFields?.length || object.tolerances?.length || object.typedBlocks || object.trajectoryId) {
36983
37524
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
36984
37525
  }
36985
37526
  }
37527
+ if (object.trajectoryId) {
37528
+ warnIfSchema30Feature(sourceSchemaVersion, diagnostics, `${node.id}.trajectory`, node.location);
37529
+ }
36986
37530
  return object;
36987
37531
  }
36988
37532
  function normalizeDraftEvent(event, rawPoses) {
@@ -36999,6 +37543,8 @@ void main() {
36999
37543
  return {
37000
37544
  objectId: rawPose.objectId,
37001
37545
  placement,
37546
+ trajectorySegmentId: parseOptionalJoinedValue(fieldMap.get("segment")?.[0]),
37547
+ trajectoryManeuverId: parseOptionalJoinedValue(fieldMap.get("maneuver")?.[0]),
37002
37548
  inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
37003
37549
  outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer"),
37004
37550
  epoch: parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]),
@@ -37214,6 +37760,19 @@ void main() {
37214
37760
  column: location.column
37215
37761
  });
37216
37762
  }
37763
+ function warnIfSchema30Feature(sourceSchemaVersion, diagnostics, featureName, location) {
37764
+ if (!isSchemaOlderThan(sourceSchemaVersion, "3.0")) {
37765
+ return;
37766
+ }
37767
+ diagnostics.push({
37768
+ code: "parse.schema30.featureCompatibility",
37769
+ severity: "warning",
37770
+ source: "parse",
37771
+ message: `Feature "${featureName}" requires schema 3.0; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
37772
+ line: location.line,
37773
+ column: location.column
37774
+ });
37775
+ }
37217
37776
  function isSchemaOlderThan(sourceSchemaVersion, requiredVersion) {
37218
37777
  return schemaVersionRank(sourceSchemaVersion) < schemaVersionRank(requiredVersion);
37219
37778
  }
@@ -37229,8 +37788,10 @@ void main() {
37229
37788
  return 3;
37230
37789
  case "2.6":
37231
37790
  return 4;
37232
- default:
37791
+ case "3.0":
37233
37792
  return 5;
37793
+ default:
37794
+ return 6;
37234
37795
  }
37235
37796
  }
37236
37797
  function preprocessAtlasSource(source) {
@@ -37320,7 +37881,7 @@ void main() {
37320
37881
  }
37321
37882
 
37322
37883
  // packages/core/dist/atlas-edit.js
37323
- function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.6") {
37884
+ function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "3.0") {
37324
37885
  return {
37325
37886
  format: "worldorbit",
37326
37887
  version,
@@ -37351,6 +37912,7 @@ void main() {
37351
37912
  groups: [],
37352
37913
  relations: [],
37353
37914
  events: [],
37915
+ trajectories: [],
37354
37916
  objects: [],
37355
37917
  diagnostics: []
37356
37918
  };
@@ -37383,6 +37945,19 @@ void main() {
37383
37945
  paths.push({ kind: "event-pose", id: event.id, key: pose.objectId });
37384
37946
  }
37385
37947
  }
37948
+ for (const trajectory of [...document2.trajectories].sort(compareIdLike2)) {
37949
+ paths.push({ kind: "trajectory", id: trajectory.id });
37950
+ for (const segment of [...trajectory.segments].sort(compareIdLike2)) {
37951
+ paths.push({ kind: "trajectory-segment", id: trajectory.id, key: segment.id });
37952
+ for (const maneuver of [...segment.maneuvers].sort(compareIdLike2)) {
37953
+ paths.push({
37954
+ kind: "trajectory-maneuver",
37955
+ id: trajectory.id,
37956
+ key: `${segment.id}:${maneuver.id}`
37957
+ });
37958
+ }
37959
+ }
37960
+ }
37386
37961
  for (const object of [...document2.objects].sort(compareIdLike2)) {
37387
37962
  paths.push({ kind: "object", id: object.id });
37388
37963
  }
@@ -37402,6 +37977,12 @@ void main() {
37402
37977
  return path.id ? findEvent(document2, path.id) : null;
37403
37978
  case "event-pose":
37404
37979
  return path.id && path.key ? findEventPose(document2, path.id, path.key) : null;
37980
+ case "trajectory":
37981
+ return path.id ? findTrajectory(document2, path.id) : null;
37982
+ case "trajectory-segment":
37983
+ return path.id && path.key ? findTrajectorySegment2(document2, path.id, path.key) : null;
37984
+ case "trajectory-maneuver":
37985
+ return path.id && path.key ? findTrajectoryManeuver2(document2, path.id, path.key) : null;
37405
37986
  case "object":
37406
37987
  return path.id ? findObject(document2, path.id) : null;
37407
37988
  case "viewpoint":
@@ -37453,6 +38034,24 @@ void main() {
37453
38034
  }
37454
38035
  upsertEventPose(next.events, path.id, value);
37455
38036
  return next;
38037
+ case "trajectory":
38038
+ if (!path.id) {
38039
+ throw new Error('Trajectory updates require an "id" value.');
38040
+ }
38041
+ upsertById(next.trajectories, value);
38042
+ return next;
38043
+ case "trajectory-segment":
38044
+ if (!path.id || !path.key) {
38045
+ throw new Error('Trajectory segment updates require a trajectory "id" and segment "key" value.');
38046
+ }
38047
+ upsertTrajectorySegment(next.trajectories, path.id, value);
38048
+ return next;
38049
+ case "trajectory-maneuver":
38050
+ if (!path.id || !path.key) {
38051
+ throw new Error('Trajectory maneuver updates require a trajectory "id" and maneuver "key" value.');
38052
+ }
38053
+ upsertTrajectoryManeuver(next.trajectories, path.id, path.key, value);
38054
+ return next;
37456
38055
  case "object":
37457
38056
  if (!path.id) {
37458
38057
  throw new Error('Object updates require an "id" value.');
@@ -37514,6 +38113,30 @@ void main() {
37514
38113
  }
37515
38114
  }
37516
38115
  return next;
38116
+ case "trajectory":
38117
+ if (path.id) {
38118
+ next.trajectories = next.trajectories.filter((trajectory) => trajectory.id !== path.id);
38119
+ }
38120
+ return next;
38121
+ case "trajectory-segment":
38122
+ if (path.id && path.key) {
38123
+ const trajectory = findTrajectory(next, path.id);
38124
+ if (trajectory) {
38125
+ trajectory.segments = trajectory.segments.filter((segment) => segment.id !== path.key);
38126
+ }
38127
+ }
38128
+ return next;
38129
+ case "trajectory-maneuver":
38130
+ if (path.id && path.key) {
38131
+ const maneuver = splitTrajectoryManeuverKey(path.key);
38132
+ if (maneuver) {
38133
+ const segment = findTrajectorySegment2(next, path.id, maneuver.segmentId);
38134
+ if (segment) {
38135
+ segment.maneuvers = segment.maneuvers.filter((entry) => entry.id !== maneuver.maneuverId);
38136
+ }
38137
+ }
38138
+ }
38139
+ return next;
37517
38140
  case "viewpoint":
37518
38141
  if (path.id) {
37519
38142
  system.viewpoints = system.viewpoints.filter((viewpoint) => viewpoint.id !== path.id);
@@ -37598,6 +38221,29 @@ void main() {
37598
38221
  };
37599
38222
  }
37600
38223
  }
38224
+ if (diagnostic.field?.startsWith("trajectory.")) {
38225
+ const parts = diagnostic.field.split(".");
38226
+ if (parts[1] && findTrajectory(document2, parts[1])) {
38227
+ if (parts[2] === "segment" && parts[3] && findTrajectorySegment2(document2, parts[1], parts[3])) {
38228
+ if (parts[4] === "maneuver" && parts[5] && findTrajectoryManeuver2(document2, parts[1], `${parts[3]}:${parts[5]}`)) {
38229
+ return {
38230
+ kind: "trajectory-maneuver",
38231
+ id: parts[1],
38232
+ key: `${parts[3]}:${parts[5]}`
38233
+ };
38234
+ }
38235
+ return {
38236
+ kind: "trajectory-segment",
38237
+ id: parts[1],
38238
+ key: parts[3]
38239
+ };
38240
+ }
38241
+ return {
38242
+ kind: "trajectory",
38243
+ id: parts[1]
38244
+ };
38245
+ }
38246
+ }
37601
38247
  if (diagnostic.field && diagnostic.field in ensureSystem(document2).atlasMetadata) {
37602
38248
  return {
37603
38249
  kind: "metadata",
@@ -37635,6 +38281,19 @@ void main() {
37635
38281
  function findEventPose(document2, eventId, objectId) {
37636
38282
  return findEvent(document2, eventId)?.positions.find((pose) => pose.objectId === objectId) ?? null;
37637
38283
  }
38284
+ function findTrajectory(document2, trajectoryId) {
38285
+ return document2.trajectories.find((trajectory) => trajectory.id === trajectoryId) ?? null;
38286
+ }
38287
+ function findTrajectorySegment2(document2, trajectoryId, segmentId) {
38288
+ return findTrajectory(document2, trajectoryId)?.segments.find((segment) => segment.id === segmentId) ?? null;
38289
+ }
38290
+ function findTrajectoryManeuver2(document2, trajectoryId, combinedKey) {
38291
+ const parsed = splitTrajectoryManeuverKey(combinedKey);
38292
+ if (!parsed) {
38293
+ return null;
38294
+ }
38295
+ return findTrajectorySegment2(document2, trajectoryId, parsed.segmentId)?.maneuvers.find((maneuver) => maneuver.id === parsed.maneuverId) ?? null;
38296
+ }
37638
38297
  function findViewpoint(system, viewpointId) {
37639
38298
  return system?.viewpoints.find((viewpoint) => viewpoint.id === viewpointId) ?? null;
37640
38299
  }
@@ -37663,6 +38322,50 @@ void main() {
37663
38322
  }
37664
38323
  event.positions[index] = value;
37665
38324
  }
38325
+ function upsertTrajectorySegment(trajectories, trajectoryId, value) {
38326
+ const trajectory = trajectories.find((entry) => entry.id === trajectoryId);
38327
+ if (!trajectory) {
38328
+ throw new Error(`Unknown trajectory "${trajectoryId}" for segment update.`);
38329
+ }
38330
+ const index = trajectory.segments.findIndex((entry) => entry.id === value.id);
38331
+ if (index === -1) {
38332
+ trajectory.segments.push(value);
38333
+ trajectory.segments.sort(compareIdLike2);
38334
+ return;
38335
+ }
38336
+ trajectory.segments[index] = value;
38337
+ }
38338
+ function upsertTrajectoryManeuver(trajectories, trajectoryId, combinedKey, value) {
38339
+ const parsed = splitTrajectoryManeuverKey(combinedKey);
38340
+ if (!parsed) {
38341
+ throw new Error(`Invalid trajectory maneuver key "${combinedKey}".`);
38342
+ }
38343
+ const trajectory = trajectories.find((entry) => entry.id === trajectoryId);
38344
+ if (!trajectory) {
38345
+ throw new Error(`Unknown trajectory "${trajectoryId}" for maneuver update.`);
38346
+ }
38347
+ const segment = trajectory.segments.find((entry) => entry.id === parsed.segmentId);
38348
+ if (!segment) {
38349
+ throw new Error(`Unknown trajectory segment "${parsed.segmentId}" on "${trajectoryId}".`);
38350
+ }
38351
+ const index = segment.maneuvers.findIndex((entry) => entry.id === value.id);
38352
+ if (index === -1) {
38353
+ segment.maneuvers.push(value);
38354
+ segment.maneuvers.sort(compareIdLike2);
38355
+ return;
38356
+ }
38357
+ segment.maneuvers[index] = value;
38358
+ }
38359
+ function splitTrajectoryManeuverKey(key) {
38360
+ const separator = key.indexOf(":");
38361
+ if (separator <= 0 || separator >= key.length - 1) {
38362
+ return null;
38363
+ }
38364
+ return {
38365
+ segmentId: key.slice(0, separator),
38366
+ maneuverId: key.slice(separator + 1)
38367
+ };
38368
+ }
37666
38369
  function compareIdLike2(left, right) {
37667
38370
  return left.id.localeCompare(right.id);
37668
38371
  }
@@ -37671,10 +38374,11 @@ void main() {
37671
38374
  }
37672
38375
 
37673
38376
  // packages/core/dist/load.js
37674
- var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1|\.5|\.6)?$/i;
38377
+ var ATLAS_SCHEMA_PATTERN = /^schema\s+(?:2(?:\.0|\.1|\.5|\.6)?|3(?:\.0)?)$/i;
37675
38378
  var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
37676
38379
  var ATLAS_SCHEMA_25_PATTERN = /^schema\s+2\.5$/i;
37677
38380
  var ATLAS_SCHEMA_26_PATTERN = /^schema\s+2\.6$/i;
38381
+ var ATLAS_SCHEMA_30_PATTERN = /^schema\s+3(?:\.0)?$/i;
37678
38382
  var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
37679
38383
  function detectWorldOrbitSchemaVersion(source) {
37680
38384
  for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
@@ -37694,6 +38398,9 @@ void main() {
37694
38398
  if (ATLAS_SCHEMA_26_PATTERN.test(trimmed)) {
37695
38399
  return "2.6";
37696
38400
  }
38401
+ if (ATLAS_SCHEMA_30_PATTERN.test(trimmed)) {
38402
+ return "3.0";
38403
+ }
37697
38404
  if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
37698
38405
  return "2.0";
37699
38406
  }
@@ -37754,7 +38461,7 @@ void main() {
37754
38461
  }
37755
38462
  function loadWorldOrbitSourceWithDiagnostics(source) {
37756
38463
  const schemaVersion = detectWorldOrbitSchemaVersion(source);
37757
- if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5" || schemaVersion === "2.6") {
38464
+ if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5" || schemaVersion === "2.6" || schemaVersion === "3.0") {
37758
38465
  return loadAtlasSourceWithDiagnostics(source, schemaVersion);
37759
38466
  }
37760
38467
  let ast;
@@ -38652,6 +39359,8 @@ void main() {
38652
39359
  case "comet":
38653
39360
  return options.outlineOnly ? `<circle cx="${x}" cy="${y}" r="${radius}" fill="transparent" stroke="${palette.stroke}" stroke-width="1.4" />` : `<path d="M ${x - radius * 2} ${y + radius * 1.3} Q ${x - radius * 0.7} ${y + radius * 0.3} ${x - radius * 0.45} ${y}" fill="none" stroke="${tail}" stroke-width="${Math.max(2, radius * 0.8)}" stroke-linecap="round" opacity="0.85" />
38654
39361
  <circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
39362
+ case "craft":
39363
+ return `<polygon points="${diamondPoints(x, y, radius * 0.85)}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
38655
39364
  case "structure":
38656
39365
  return `<polygon points="${diamondPoints(x, y, radius)}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
38657
39366
  case "phenomenon": {
@@ -38785,6 +39494,8 @@ void main() {
38785
39494
  return { fill: "#9ce7ff", stroke: "#e7fbff" };
38786
39495
  case "ring":
38787
39496
  return { fill: "#e59f7d", stroke: "#fff0d3" };
39497
+ case "craft":
39498
+ return { fill: "#ffb47f", stroke: "#fff0d3" };
38788
39499
  case "structure":
38789
39500
  return { fill: theme.accentStrong, stroke: "#fff2ea" };
38790
39501
  case "phenomenon":
@@ -39050,6 +39761,8 @@ void main() {
39050
39761
  return "#a7a5b8";
39051
39762
  case "comet":
39052
39763
  return "#9ce7ff";
39764
+ case "craft":
39765
+ return "#ffb47f";
39053
39766
  case "structure":
39054
39767
  return "#ff7f5f";
39055
39768
  case "phenomenon":
@@ -39708,6 +40421,8 @@ void main() {
39708
40421
  return "#b8926a";
39709
40422
  case "ring":
39710
40423
  return "#cdbf9a";
40424
+ case "craft":
40425
+ return "#ffce8a";
39711
40426
  case "structure":
39712
40427
  return "#ffce8a";
39713
40428
  case "phenomenon":
@@ -40964,7 +41679,7 @@ void main() {
40964
41679
  tooltipRoot.hidden = false;
40965
41680
  tooltipRoot.dataset.mode = resolved.mode;
40966
41681
  tooltipRoot.classList.toggle("is-pinned", resolved.mode === "pinned");
40967
- tooltipRoot.style.pointerEvents = "auto";
41682
+ tooltipRoot.style.pointerEvents = resolved.mode === "pinned" ? "auto" : "none";
40968
41683
  tooltipRoot.style.visibility = "hidden";
40969
41684
  renderTooltipContent(tooltipRoot, tooltipDetails, resolved.mode);
40970
41685
  positionTooltip(tooltipRoot, details.renderObject);
@@ -41557,7 +42272,11 @@ void main() {
41557
42272
  if (!(target instanceof Element)) {
41558
42273
  return null;
41559
42274
  }
41560
- return target.closest("[data-object-id]")?.dataset.objectId ?? null;
42275
+ const selectionTarget = target.closest("[data-object-id], [data-orbit-object-id]");
42276
+ if (!selectionTarget) {
42277
+ return null;
42278
+ }
42279
+ return selectionTarget.dataset.objectId ?? selectionTarget.dataset.orbitObjectId ?? null;
41561
42280
  }
41562
42281
  function ensureBrowserEnvironment(container) {
41563
42282
  if (typeof window === "undefined" || typeof document === "undefined") {
@@ -41689,7 +42408,7 @@ void main() {
41689
42408
  backdrop-filter: blur(12px);
41690
42409
  font: 500 13px/1.5 "Segoe UI Variable", "Segoe UI", sans-serif;
41691
42410
  }
41692
- .wo-viewer-tooltip-root[data-mode="hover"] { pointer-events: auto; }
42411
+ .wo-viewer-tooltip-root[data-mode="hover"] { pointer-events: none; }
41693
42412
  .wo-viewer-tooltip-root[data-mode="pinned"] { pointer-events: auto; }
41694
42413
  .wo-tooltip-card { display: grid; gap: 10px; }
41695
42414
  .wo-tooltip-head { display: grid; grid-template-columns: 52px minmax(0, 1fr); gap: 12px; align-items: center; }
@@ -42503,6 +43222,205 @@ void main() {
42503
43222
  function parseSource(source) {
42504
43223
  return loadWorldOrbitSource(source).document;
42505
43224
  }
43225
+
43226
+ // packages/viewer/dist/interactive-2d.js
43227
+ var DEFAULT_VIEWER_LIMITS2 = {
43228
+ minScale: 0.2,
43229
+ maxScale: 8,
43230
+ fitPadding: 48
43231
+ };
43232
+ function createInteractiveViewer2D(container, scene, options = {}) {
43233
+ const constraints = {
43234
+ minScale: options.minScale ?? DEFAULT_VIEWER_LIMITS2.minScale,
43235
+ maxScale: options.maxScale ?? DEFAULT_VIEWER_LIMITS2.maxScale,
43236
+ fitPadding: options.fitPadding ?? DEFAULT_VIEWER_LIMITS2.fitPadding
43237
+ };
43238
+ const behavior = {
43239
+ pointer: options.pointer ?? true,
43240
+ touch: options.touch ?? true,
43241
+ selection: options.selection ?? true
43242
+ };
43243
+ let renderOptions = {
43244
+ width: options.width,
43245
+ height: options.height,
43246
+ padding: options.padding,
43247
+ preset: options.preset,
43248
+ theme: options.theme,
43249
+ layers: options.layers,
43250
+ subtitle: options.subtitle,
43251
+ pointer: behavior.pointer,
43252
+ touch: behavior.touch,
43253
+ selection: behavior.selection,
43254
+ minScale: constraints.minScale,
43255
+ maxScale: constraints.maxScale,
43256
+ fitPadding: constraints.fitPadding
43257
+ };
43258
+ let state = fitViewerState(scene, DEFAULT_VIEWER_STATE, constraints);
43259
+ let svgElement = null;
43260
+ let cameraRoot = null;
43261
+ let destroyed = false;
43262
+ let activePointerId = null;
43263
+ let lastPointerClientPoint = null;
43264
+ let dragDistance = 0;
43265
+ const previousTabIndex = container.getAttribute("tabindex");
43266
+ const previousTouchAction = container.style.touchAction;
43267
+ if (previousTabIndex === null) {
43268
+ container.tabIndex = 0;
43269
+ }
43270
+ container.classList.add("wo-viewer-container");
43271
+ container.style.touchAction = behavior.touch ? "none" : previousTouchAction;
43272
+ const handleWheel = (event) => {
43273
+ if (!behavior.pointer || destroyed || !svgElement) {
43274
+ return;
43275
+ }
43276
+ event.preventDefault();
43277
+ container.focus();
43278
+ const anchor = getScenePointFromClient(event.clientX, event.clientY);
43279
+ const factor = clamp2(Math.exp(-event.deltaY * 2e-3), 0.6, 1.6);
43280
+ updateState(zoomViewerStateAt(scene, state, factor, anchor, constraints));
43281
+ };
43282
+ const handlePointerDown = (event) => {
43283
+ if (destroyed) {
43284
+ return;
43285
+ }
43286
+ const isTouch = event.pointerType === "touch";
43287
+ if (isTouch && !behavior.touch || !isTouch && !behavior.pointer) {
43288
+ return;
43289
+ }
43290
+ if (!isTouch && event.button !== 0) {
43291
+ return;
43292
+ }
43293
+ activePointerId = event.pointerId;
43294
+ lastPointerClientPoint = { x: event.clientX, y: event.clientY };
43295
+ dragDistance = 0;
43296
+ container.setPointerCapture?.(event.pointerId);
43297
+ container.focus();
43298
+ };
43299
+ const handlePointerMove = (event) => {
43300
+ if (destroyed || activePointerId !== event.pointerId || !lastPointerClientPoint) {
43301
+ return;
43302
+ }
43303
+ const rect = svgElement?.getBoundingClientRect();
43304
+ if (!rect || rect.width <= 0 || rect.height <= 0) {
43305
+ return;
43306
+ }
43307
+ const dx = event.clientX - lastPointerClientPoint.x;
43308
+ const dy = event.clientY - lastPointerClientPoint.y;
43309
+ lastPointerClientPoint = { x: event.clientX, y: event.clientY };
43310
+ dragDistance += Math.hypot(dx, dy);
43311
+ updateState(panViewerState(state, dx * (scene.width / rect.width), dy * (scene.height / rect.height)));
43312
+ };
43313
+ const stopPointer = (event) => {
43314
+ if (activePointerId !== event.pointerId) {
43315
+ return;
43316
+ }
43317
+ activePointerId = null;
43318
+ lastPointerClientPoint = null;
43319
+ container.releasePointerCapture?.(event.pointerId);
43320
+ };
43321
+ const handleClick = (event) => {
43322
+ if (destroyed || !behavior.selection || dragDistance > 6) {
43323
+ return;
43324
+ }
43325
+ const objectEl = event.target?.closest(".wo-object[data-object-id]");
43326
+ if (!objectEl) {
43327
+ return;
43328
+ }
43329
+ updateState({
43330
+ ...state,
43331
+ selectedObjectId: objectEl.dataset.objectId ?? null
43332
+ });
43333
+ renderSvg();
43334
+ };
43335
+ container.addEventListener("wheel", handleWheel, { passive: false });
43336
+ container.addEventListener("pointerdown", handlePointerDown);
43337
+ container.addEventListener("pointermove", handlePointerMove);
43338
+ container.addEventListener("pointerup", stopPointer);
43339
+ container.addEventListener("pointercancel", stopPointer);
43340
+ container.addEventListener("click", handleClick);
43341
+ renderSvg();
43342
+ return {
43343
+ getState() {
43344
+ return { ...state };
43345
+ },
43346
+ setState(nextState) {
43347
+ updateState({
43348
+ ...state,
43349
+ ...nextState
43350
+ });
43351
+ if ("selectedObjectId" in nextState) {
43352
+ renderSvg();
43353
+ }
43354
+ },
43355
+ setRenderOptions(nextOptions) {
43356
+ renderOptions = {
43357
+ ...renderOptions,
43358
+ ...nextOptions
43359
+ };
43360
+ renderSvg();
43361
+ },
43362
+ fitToSystem() {
43363
+ updateState(fitViewerState(scene, state, constraints));
43364
+ },
43365
+ destroy() {
43366
+ if (destroyed) {
43367
+ return;
43368
+ }
43369
+ destroyed = true;
43370
+ container.removeEventListener("wheel", handleWheel);
43371
+ container.removeEventListener("pointerdown", handlePointerDown);
43372
+ container.removeEventListener("pointermove", handlePointerMove);
43373
+ container.removeEventListener("pointerup", stopPointer);
43374
+ container.removeEventListener("pointercancel", stopPointer);
43375
+ container.removeEventListener("click", handleClick);
43376
+ if (previousTabIndex === null) {
43377
+ container.removeAttribute("tabindex");
43378
+ }
43379
+ container.style.touchAction = previousTouchAction;
43380
+ container.replaceChildren();
43381
+ svgElement = null;
43382
+ cameraRoot = null;
43383
+ }
43384
+ };
43385
+ function renderSvg() {
43386
+ if (destroyed) {
43387
+ return;
43388
+ }
43389
+ container.innerHTML = renderSceneToSvg(scene, {
43390
+ width: renderOptions.width,
43391
+ height: renderOptions.height,
43392
+ padding: renderOptions.padding,
43393
+ preset: renderOptions.preset,
43394
+ theme: renderOptions.theme,
43395
+ layers: renderOptions.layers,
43396
+ subtitle: renderOptions.subtitle,
43397
+ selectedObjectId: state.selectedObjectId
43398
+ });
43399
+ svgElement = container.querySelector("svg");
43400
+ cameraRoot = container.querySelector(`[data-worldorbit-camera-root="${WORLD_LAYER_ID}"]`);
43401
+ applyTransform();
43402
+ }
43403
+ function applyTransform() {
43404
+ cameraRoot?.setAttribute("transform", composeViewerTransform(scene, state));
43405
+ }
43406
+ function updateState(nextState) {
43407
+ state = nextState;
43408
+ applyTransform();
43409
+ }
43410
+ function getScenePointFromClient(clientX, clientY) {
43411
+ const rect = svgElement?.getBoundingClientRect();
43412
+ if (!rect || rect.width <= 0 || rect.height <= 0) {
43413
+ return { x: scene.width / 2, y: scene.height / 2 };
43414
+ }
43415
+ return {
43416
+ x: (clientX - rect.left) / rect.width * scene.width,
43417
+ y: (clientY - rect.top) / rect.height * scene.height
43418
+ };
43419
+ }
43420
+ }
43421
+ function clamp2(value, min, max) {
43422
+ return Math.min(Math.max(value, min), max);
43423
+ }
42506
43424
  return __toCommonJS(worldorbit_esm_exports);
42507
43425
  })();
42508
43426
  /**