worldorbit 3.2.2 → 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 (73) 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/viewer/dist/minimap.js +9 -7
  20. package/dist/browser/viewer/dist/render.js +23 -19
  21. package/dist/browser/viewer/dist/runtime-3d.js +2 -0
  22. package/dist/browser/viewer/dist/viewer.js +7 -3
  23. package/dist/obsidian-plugin/README.md +141 -124
  24. package/dist/obsidian-plugin/main.js +31 -31
  25. package/dist/unpkg/core/dist/atlas-edit.js +146 -1
  26. package/dist/unpkg/core/dist/atlas-validate.js +105 -10
  27. package/dist/unpkg/core/dist/draft-parse.js +341 -16
  28. package/dist/unpkg/core/dist/draft.d.ts +2 -1
  29. package/dist/unpkg/core/dist/draft.js +25 -3
  30. package/dist/unpkg/core/dist/format.js +86 -4
  31. package/dist/unpkg/core/dist/index.d.ts +1 -0
  32. package/dist/unpkg/core/dist/index.js +1 -0
  33. package/dist/unpkg/core/dist/load.js +7 -2
  34. package/dist/unpkg/core/dist/normalize.js +1 -0
  35. package/dist/unpkg/core/dist/scene.js +11 -2
  36. package/dist/unpkg/core/dist/schema.js +11 -1
  37. package/dist/unpkg/core/dist/solver.d.ts +26 -0
  38. package/dist/unpkg/core/dist/solver.js +27 -0
  39. package/dist/unpkg/core/dist/types.d.ts +57 -3
  40. package/dist/unpkg/editor/dist/editor.js +844 -719
  41. package/dist/unpkg/editor/dist/types.d.ts +2 -1
  42. package/dist/unpkg/viewer/dist/minimap.js +9 -7
  43. package/dist/unpkg/viewer/dist/render.js +23 -19
  44. package/dist/unpkg/viewer/dist/runtime-3d.js +2 -0
  45. package/dist/unpkg/viewer/dist/viewer.js +7 -3
  46. package/dist/unpkg/worldorbit-core.min.js +10 -10
  47. package/dist/unpkg/worldorbit-editor.min.js +359 -332
  48. package/dist/unpkg/worldorbit-markdown.min.js +28 -28
  49. package/dist/unpkg/worldorbit-viewer.min.js +214 -214
  50. package/dist/unpkg/worldorbit.js +758 -40
  51. package/dist/unpkg/worldorbit.min.js +223 -223
  52. package/package.json +5 -1
  53. package/packages/core/dist/atlas-edit.js +146 -1
  54. package/packages/core/dist/atlas-validate.js +105 -10
  55. package/packages/core/dist/draft-parse.js +341 -16
  56. package/packages/core/dist/draft.d.ts +2 -1
  57. package/packages/core/dist/draft.js +25 -3
  58. package/packages/core/dist/format.js +86 -4
  59. package/packages/core/dist/index.d.ts +1 -0
  60. package/packages/core/dist/index.js +1 -0
  61. package/packages/core/dist/load.js +7 -2
  62. package/packages/core/dist/normalize.js +1 -0
  63. package/packages/core/dist/scene.js +11 -2
  64. package/packages/core/dist/schema.js +11 -1
  65. package/packages/core/dist/solver.d.ts +26 -0
  66. package/packages/core/dist/solver.js +27 -0
  67. package/packages/core/dist/types.d.ts +57 -3
  68. package/packages/editor/dist/editor.js +844 -719
  69. package/packages/editor/dist/types.d.ts +2 -1
  70. package/packages/viewer/dist/minimap.js +9 -7
  71. package/packages/viewer/dist/render.js +23 -19
  72. package/packages/viewer/dist/runtime-3d.js +2 -0
  73. package/packages/viewer/dist/viewer.js +7 -3
@@ -30713,6 +30713,7 @@ void main() {
30713
30713
  createEmptyAtlasDocument: () => createEmptyAtlasDocument,
30714
30714
  createInteractiveViewer: () => createInteractiveViewer,
30715
30715
  createInteractiveViewer2D: () => createInteractiveViewer2D,
30716
+ createTrajectorySolverSnapshot: () => createTrajectorySolverSnapshot,
30716
30717
  createWorldOrbitEmbedMarkup: () => createWorldOrbitEmbedMarkup,
30717
30718
  defineWorldOrbitViewerElement: () => defineWorldOrbitViewerElement,
30718
30719
  deserializeViewerAtlasState: () => deserializeViewerAtlasState,
@@ -30810,6 +30811,7 @@ void main() {
30810
30811
  "asteroid",
30811
30812
  "comet",
30812
30813
  "ring",
30814
+ "craft",
30813
30815
  "structure",
30814
30816
  "phenomenon"
30815
30817
  ];
@@ -30820,10 +30822,11 @@ void main() {
30820
30822
  "moon",
30821
30823
  "asteroid",
30822
30824
  "comet",
30825
+ "craft",
30823
30826
  "structure",
30824
30827
  "phenomenon"
30825
30828
  ];
30826
- var ANCHORED_OBJECTS = ["structure", "phenomenon"];
30829
+ var ANCHORED_OBJECTS = ["craft", "structure", "phenomenon"];
30827
30830
  var ORBITAL_OBJECTS = [
30828
30831
  "star",
30829
30832
  "planet",
@@ -30832,6 +30835,7 @@ void main() {
30832
30835
  "asteroid",
30833
30836
  "comet",
30834
30837
  "ring",
30838
+ "craft",
30835
30839
  "structure",
30836
30840
  "phenomenon"
30837
30841
  ];
@@ -30843,6 +30847,7 @@ void main() {
30843
30847
  "asteroid",
30844
30848
  "comet",
30845
30849
  "ring",
30850
+ "craft",
30846
30851
  "structure",
30847
30852
  "phenomenon"
30848
30853
  ];
@@ -31071,6 +31076,12 @@ void main() {
31071
31076
  arity: "single",
31072
31077
  objectTypes: NON_SYSTEM_OBJECTS,
31073
31078
  unitFamily: "duration"
31079
+ }),
31080
+ createField("trajectory", {
31081
+ kind: "string",
31082
+ placement: false,
31083
+ arity: "single",
31084
+ objectTypes: ["craft", "structure"]
31074
31085
  })
31075
31086
  ].map((schema) => [schema.key, schema]));
31076
31087
  var WORLDORBIT_FIELD_KEYS = new Set(WORLDORBIT_FIELD_SCHEMAS.keys());
@@ -31384,6 +31395,7 @@ void main() {
31384
31395
  groups: [],
31385
31396
  relations: [],
31386
31397
  events: [],
31398
+ trajectories: [],
31387
31399
  objects
31388
31400
  };
31389
31401
  }
@@ -32304,7 +32316,7 @@ void main() {
32304
32316
  anchorX,
32305
32317
  anchorY,
32306
32318
  label: object.id,
32307
- 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,
32308
32320
  fillColor: customColorFor(object.properties.color),
32309
32321
  imageHref: typeof object.properties.image === "string" && object.properties.image.trim() ? object.properties.image : void 0,
32310
32322
  hidden: object.properties.hidden === true
@@ -32398,6 +32410,7 @@ void main() {
32398
32410
  case "asteroid":
32399
32411
  case "comet":
32400
32412
  return 4;
32413
+ case "craft":
32401
32414
  case "structure":
32402
32415
  case "phenomenon":
32403
32416
  return 5;
@@ -32422,7 +32435,7 @@ void main() {
32422
32435
  const oppositeVertical = vertical === "below" ? "above" : "below";
32423
32436
  const horizontal = defaultHorizontalDirection(object, parent, sceneWidth);
32424
32437
  const oppositeHorizontal = horizontal === "right" ? "left" : "right";
32425
- 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";
32426
32439
  return preferHorizontal ? [horizontal, vertical, oppositeHorizontal, oppositeVertical] : [vertical, horizontal, oppositeVertical, oppositeHorizontal];
32427
32440
  }
32428
32441
  function defaultVerticalDirection(object, parent, sceneHeight) {
@@ -32886,7 +32899,7 @@ void main() {
32886
32899
  return next;
32887
32900
  }
32888
32901
  function parseViewpointObjectTypes(value) {
32889
- 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");
32890
32903
  }
32891
32904
  function parseViewpointGroups(value, document2, relationships, objectMap) {
32892
32905
  return splitListValue(value).map((entry) => {
@@ -33507,6 +33520,8 @@ void main() {
33507
33520
  return clampNumber(6 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
33508
33521
  case "ring":
33509
33522
  return clampNumber(5 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
33523
+ case "craft":
33524
+ return clampNumber(5 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
33510
33525
  case "structure":
33511
33526
  return clampNumber(6 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
33512
33527
  case "phenomenon":
@@ -33520,6 +33535,8 @@ void main() {
33520
33535
  return radius * 2.4;
33521
33536
  case "phenomenon":
33522
33537
  return radius * 1.25;
33538
+ case "craft":
33539
+ return radius + 1.5;
33523
33540
  case "structure":
33524
33541
  return radius + 2;
33525
33542
  default:
@@ -34085,6 +34102,35 @@ void main() {
34085
34102
  return value * Math.PI / 180;
34086
34103
  }
34087
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
+
34088
34134
  // packages/core/dist/draft.js
34089
34135
  function upgradeDocumentToV2(document2, options = {}) {
34090
34136
  const scene = renderDocumentToScene(document2, options);
@@ -34103,15 +34149,16 @@ void main() {
34103
34149
  }
34104
34150
  return {
34105
34151
  format: "worldorbit",
34106
- version: "2.6",
34107
- schemaVersion: "2.6",
34152
+ version: "3.0",
34153
+ schemaVersion: "3.0",
34108
34154
  sourceVersion: document2.version,
34109
34155
  theme: document2.theme ?? null,
34110
34156
  system,
34111
34157
  groups: structuredClone(document2.groups ?? []),
34112
34158
  relations: structuredClone(document2.relations ?? []),
34113
34159
  events: structuredClone(document2.events ?? []),
34114
- objects: document2.objects.map(cloneWorldOrbitObject),
34160
+ trajectories: [],
34161
+ objects: document2.objects.map(cloneWorldOrbitObject).map(normalizeLegacyCraftObject),
34115
34162
  diagnostics
34116
34163
  };
34117
34164
  }
@@ -34140,6 +34187,7 @@ void main() {
34140
34187
  groups: structuredClone(document2.groups ?? []),
34141
34188
  relations: structuredClone(document2.relations ?? []),
34142
34189
  events: document2.events.map(cloneWorldOrbitEvent),
34190
+ trajectories: document2.trajectories.map(cloneWorldOrbitTrajectory),
34143
34191
  objects
34144
34192
  };
34145
34193
  }
@@ -34287,6 +34335,7 @@ void main() {
34287
34335
  function cloneWorldOrbitObject(object) {
34288
34336
  return {
34289
34337
  ...object,
34338
+ trajectoryId: object.trajectoryId ?? null,
34290
34339
  groups: object.groups ? [...object.groups] : void 0,
34291
34340
  resonance: object.resonance ? { ...object.resonance } : object.resonance,
34292
34341
  renderHints: object.renderHints ? { ...object.renderHints } : object.renderHints,
@@ -34306,6 +34355,7 @@ void main() {
34306
34355
  function cloneWorldOrbitEvent(event) {
34307
34356
  return {
34308
34357
  ...event,
34358
+ trajectoryId: event.trajectoryId ?? null,
34309
34359
  participantObjectIds: [...event.participantObjectIds],
34310
34360
  tags: [...event.tags],
34311
34361
  positions: event.positions.map(cloneWorldOrbitEventPose)
@@ -34315,12 +34365,30 @@ void main() {
34315
34365
  return {
34316
34366
  objectId: pose.objectId,
34317
34367
  placement: clonePlacement(pose.placement),
34368
+ trajectorySegmentId: pose.trajectorySegmentId ?? null,
34369
+ trajectoryManeuverId: pose.trajectoryManeuverId ?? null,
34318
34370
  inner: pose.inner ? { ...pose.inner } : void 0,
34319
34371
  outer: pose.outer ? { ...pose.outer } : void 0,
34320
34372
  epoch: pose.epoch ?? null,
34321
34373
  referencePlane: pose.referencePlane ?? null
34322
34374
  };
34323
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
+ }
34324
34392
  function clonePlacement(placement) {
34325
34393
  return placement ? structuredClone(placement) : null;
34326
34394
  }
@@ -34561,7 +34629,7 @@ void main() {
34561
34629
  ];
34562
34630
  function formatDocument(document2, options = {}) {
34563
34631
  const schema = options.schema ?? "auto";
34564
- 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";
34565
34633
  if (useDraft) {
34566
34634
  if (schema === "2.0-draft") {
34567
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" ? {
@@ -34571,12 +34639,12 @@ void main() {
34571
34639
  } : upgradeDocumentToDraftV2(document2);
34572
34640
  return formatDraftDocument(legacyDraftDocument);
34573
34641
  }
34574
- 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" ? {
34575
34643
  ...document2,
34576
34644
  version: "2.0",
34577
34645
  schemaVersion: "2.0"
34578
34646
  } : upgradeDocumentToV2(document2);
34579
- 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) {
34580
34648
  return formatAtlasDocument({
34581
34649
  ...atlasDocument,
34582
34650
  version: schema,
@@ -34616,6 +34684,10 @@ void main() {
34616
34684
  lines.push("");
34617
34685
  lines.push(...formatAtlasEvent(event));
34618
34686
  }
34687
+ for (const trajectory of [...document2.trajectories].sort(compareIdLike)) {
34688
+ lines.push("");
34689
+ lines.push(...formatAtlasTrajectory(trajectory));
34690
+ }
34619
34691
  const sortedObjects = [...document2.objects].sort(compareObjects);
34620
34692
  if (sortedObjects.length > 0 && lines.at(-1) !== "") {
34621
34693
  lines.push("");
@@ -34799,6 +34871,9 @@ void main() {
34799
34871
  if (object.groups?.length) {
34800
34872
  lines.push(`groups ${object.groups.join(" ")}`);
34801
34873
  }
34874
+ if (object.trajectoryId) {
34875
+ lines.push(`trajectory ${object.trajectoryId}`);
34876
+ }
34802
34877
  if (object.epoch) {
34803
34878
  lines.push(`epoch ${quoteIfNeeded(object.epoch)}`);
34804
34879
  }
@@ -34959,6 +35034,9 @@ void main() {
34959
35034
  if (event.summary) {
34960
35035
  lines.push(` summary ${quoteIfNeeded(event.summary)}`);
34961
35036
  }
35037
+ if (event.trajectoryId) {
35038
+ lines.push(` trajectory ${event.trajectoryId}`);
35039
+ }
34962
35040
  if (event.targetObjectId) {
34963
35041
  lines.push(` target ${event.targetObjectId}`);
34964
35042
  }
@@ -35001,12 +35079,80 @@ void main() {
35001
35079
  function formatEventPoseFields(pose) {
35002
35080
  return [
35003
35081
  ...formatPlacement(pose.placement),
35082
+ ...pose.trajectorySegmentId ? [`segment ${pose.trajectorySegmentId}`] : [],
35083
+ ...pose.trajectoryManeuverId ? [`maneuver ${pose.trajectoryManeuverId}`] : [],
35004
35084
  ...pose.epoch ? [`epoch ${quoteIfNeeded(pose.epoch)}`] : [],
35005
35085
  ...pose.referencePlane ? [`referencePlane ${quoteIfNeeded(pose.referencePlane)}`] : [],
35006
35086
  ...formatOptionalUnit("inner", pose.inner),
35007
35087
  ...formatOptionalUnit("outer", pose.outer)
35008
35088
  ];
35009
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
+ }
35010
35156
  function hasCameraValues(camera) {
35011
35157
  return camera.azimuth !== null || camera.elevation !== null || camera.roll !== null || camera.distance !== null;
35012
35158
  }
@@ -35098,10 +35244,12 @@ void main() {
35098
35244
  return 5;
35099
35245
  case "ring":
35100
35246
  return 6;
35101
- case "structure":
35247
+ case "craft":
35102
35248
  return 7;
35103
- case "phenomenon":
35249
+ case "structure":
35104
35250
  return 8;
35251
+ case "phenomenon":
35252
+ return 9;
35105
35253
  }
35106
35254
  }
35107
35255
  function quoteIfNeeded(value) {
@@ -35278,6 +35426,7 @@ void main() {
35278
35426
  const objectMap = new Map(document2.objects.map((object) => [object.id, object]));
35279
35427
  const groupIds = new Set(document2.groups.map((group) => group.id));
35280
35428
  const eventIds = new Set(document2.events.map((event) => event.id));
35429
+ const trajectoryMap = new Map(document2.trajectories.map((trajectory) => [trajectory.id, trajectory]));
35281
35430
  if (!document2.system) {
35282
35431
  diagnostics.push(error("validate.system.required", "Atlas documents must declare exactly one system."));
35283
35432
  }
@@ -35288,6 +35437,7 @@ void main() {
35288
35437
  ["annotation", document2.system?.annotations.map((annotation) => annotation.id) ?? []],
35289
35438
  ["relation", document2.relations.map((relation) => relation.id)],
35290
35439
  ["event", document2.events.map((event) => event.id)],
35440
+ ["trajectory", document2.trajectories.map((trajectory) => trajectory.id)],
35291
35441
  ["object", document2.objects.map((object) => object.id)]
35292
35442
  ]) {
35293
35443
  for (const id of ids) {
@@ -35306,10 +35456,13 @@ void main() {
35306
35456
  validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap);
35307
35457
  }
35308
35458
  for (const object of document2.objects) {
35309
- validateObject(object, document2.system, objectMap, groupIds, diagnostics);
35459
+ validateObject(object, document2.system, objectMap, groupIds, trajectoryMap, diagnostics);
35310
35460
  }
35311
35461
  for (const event of document2.events) {
35312
- 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);
35313
35466
  }
35314
35467
  return diagnostics;
35315
35468
  }
@@ -35347,7 +35500,7 @@ void main() {
35347
35500
  validateProjection(viewpoint.projection, diagnostics, `viewpoint.${viewpoint.id}.projection`, viewpoint.id);
35348
35501
  validateCamera(viewpoint.camera, viewpoint.projection, viewpoint.rotationDeg, diagnostics, viewpoint.id, viewpoint.focusObjectId, viewpoint.selectedObjectId, filter, objectMap);
35349
35502
  }
35350
- function validateObject(object, system, objectMap, groupIds, diagnostics) {
35503
+ function validateObject(object, system, objectMap, groupIds, trajectoryMap, diagnostics) {
35351
35504
  const placement = object.placement;
35352
35505
  const orbitPlacement = placement?.mode === "orbit" ? placement : null;
35353
35506
  const parentObject = placement?.mode === "orbit" ? objectMap.get(placement.target) ?? null : null;
@@ -35364,6 +35517,13 @@ void main() {
35364
35517
  if (typeof object.referencePlane === "string" && !object.referencePlane.trim()) {
35365
35518
  diagnostics.push(warn("validate.referencePlane.empty", `Object "${object.id}" defines an empty reference plane string.`, object.id, "referencePlane"));
35366
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
+ }
35367
35527
  if (orbitPlacement) {
35368
35528
  if (!objectMap.has(orbitPlacement.target)) {
35369
35529
  diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
@@ -35390,8 +35550,8 @@ void main() {
35390
35550
  }
35391
35551
  }
35392
35552
  if (placement?.mode === "at") {
35393
- if (object.type !== "structure" && object.type !== "phenomenon") {
35394
- 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"));
35395
35555
  }
35396
35556
  if (!validateAtTarget(object, objectMap, diagnostics)) {
35397
35557
  diagnostics.push(error("validate.at.target.unknown", `Unknown at-reference target "${placement.target}" on "${object.id}".`, object.id, "at"));
@@ -35435,7 +35595,7 @@ void main() {
35435
35595
  }
35436
35596
  }
35437
35597
  }
35438
- function validateEvent(event, system, objectMap, diagnostics) {
35598
+ function validateEvent(event, system, objectMap, trajectoryMap, diagnostics) {
35439
35599
  const fieldPrefix = `event.${event.id}`;
35440
35600
  const referencedIds = /* @__PURE__ */ new Set();
35441
35601
  if (!event.kind.trim()) {
@@ -35447,6 +35607,9 @@ void main() {
35447
35607
  if (typeof event.referencePlane === "string" && !event.referencePlane.trim()) {
35448
35608
  diagnostics.push(warn("validate.event.referencePlane.empty", `Event "${event.id}" defines an empty reference plane string.`, void 0, `${fieldPrefix}.referencePlane`));
35449
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
+ }
35450
35613
  if (!event.targetObjectId && event.participantObjectIds.length === 0) {
35451
35614
  diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
35452
35615
  }
@@ -35493,19 +35656,25 @@ void main() {
35493
35656
  if (!referencedIds.has(pose.objectId)) {
35494
35657
  diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
35495
35658
  }
35496
- validateEventPose(pose, object, event, system, objectMap, diagnostics, poseFieldPrefix, event.id);
35659
+ validateEventPose(pose, object, event, system, objectMap, trajectoryMap, diagnostics, poseFieldPrefix, event.id);
35497
35660
  }
35498
35661
  const missingPoseIds = [...referencedIds].filter((objectId) => !poseIds.has(objectId));
35499
35662
  if (event.positions.length > 0 && missingPoseIds.length > 0) {
35500
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`));
35501
35664
  }
35502
35665
  }
35503
- function validateEventPose(pose, object, event, system, objectMap, diagnostics, fieldPrefix, eventId) {
35666
+ function validateEventPose(pose, object, event, system, objectMap, trajectoryMap, diagnostics, fieldPrefix, eventId) {
35504
35667
  const placement = pose.placement;
35505
35668
  if (!placement) {
35506
35669
  diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
35507
35670
  return;
35508
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
+ }
35509
35678
  if (placement.mode === "orbit") {
35510
35679
  if (!objectMap.has(placement.target)) {
35511
35680
  diagnostics.push(error("validate.event.pose.orbit.target.unknown", `Unknown event orbit target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.orbit`));
@@ -35534,8 +35703,8 @@ void main() {
35534
35703
  return;
35535
35704
  }
35536
35705
  if (placement.mode === "at") {
35537
- if (object.type !== "structure" && object.type !== "phenomenon") {
35538
- 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`));
35539
35708
  }
35540
35709
  const reference = placement.reference;
35541
35710
  if (reference.kind === "named" && !objectMap.has(reference.name)) {
@@ -35551,6 +35720,78 @@ void main() {
35551
35720
  }
35552
35721
  }
35553
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
+ }
35554
35795
  function validateAtTarget(object, objectMap, diagnostics) {
35555
35796
  const reference = object.placement?.mode === "at" ? object.placement.reference : null;
35556
35797
  if (!reference) {
@@ -35731,6 +35972,14 @@ void main() {
35731
35972
  "habitability",
35732
35973
  "settlement"
35733
35974
  ]);
35975
+ var TRAJECTORY_SEGMENT_KINDS = /* @__PURE__ */ new Set([
35976
+ "departure",
35977
+ "transfer",
35978
+ "flyby",
35979
+ "capture",
35980
+ "stationkeeping",
35981
+ "escape"
35982
+ ]);
35734
35983
  var DRAFT_OBJECT_FIELD_SPECS = /* @__PURE__ */ new Map();
35735
35984
  for (const key of [
35736
35985
  "orbit",
@@ -35762,7 +36011,8 @@ void main() {
35762
36011
  "outer",
35763
36012
  "on",
35764
36013
  "source",
35765
- "cycle"
36014
+ "cycle",
36015
+ "trajectory"
35766
36016
  ]) {
35767
36017
  const schema = getFieldSchema(key);
35768
36018
  if (schema) {
@@ -35787,11 +36037,12 @@ void main() {
35787
36037
  { key: "derive", inlineMode: "pair", allowRepeat: true },
35788
36038
  { key: "validate", inlineMode: "single", allowRepeat: true },
35789
36039
  { key: "locked", inlineMode: "multiple", allowRepeat: false },
35790
- { key: "tolerance", inlineMode: "pair", allowRepeat: true }
36040
+ { key: "tolerance", inlineMode: "pair", allowRepeat: true },
36041
+ { key: "trajectory", inlineMode: "single", allowRepeat: false }
35791
36042
  ]) {
35792
36043
  DRAFT_OBJECT_FIELD_SPECS.set(spec.key, {
35793
36044
  key: spec.key,
35794
- version: "2.1",
36045
+ version: spec.key === "trajectory" ? "3.0" : "2.1",
35795
36046
  inlineMode: spec.inlineMode,
35796
36047
  allowRepeat: spec.allowRepeat
35797
36048
  });
@@ -35812,7 +36063,9 @@ void main() {
35812
36063
  "inner",
35813
36064
  "outer",
35814
36065
  "epoch",
35815
- "referencePlane"
36066
+ "referencePlane",
36067
+ "segment",
36068
+ "maneuver"
35816
36069
  ]);
35817
36070
  function parseWorldOrbitAtlas(source) {
35818
36071
  return parseAtlasSource(source);
@@ -35832,6 +36085,7 @@ void main() {
35832
36085
  const groups = [];
35833
36086
  const relations = [];
35834
36087
  const events = [];
36088
+ const trajectories = [];
35835
36089
  const eventPoseNodes = /* @__PURE__ */ new Map();
35836
36090
  let sawDefaults = false;
35837
36091
  let sawAtlas = false;
@@ -35840,6 +36094,7 @@ void main() {
35840
36094
  const groupIds = /* @__PURE__ */ new Set();
35841
36095
  const relationIds = /* @__PURE__ */ new Set();
35842
36096
  const eventIds = /* @__PURE__ */ new Set();
36097
+ const trajectoryIds = /* @__PURE__ */ new Set();
35843
36098
  for (let index = 0; index < lines.length; index++) {
35844
36099
  const rawLine = lines[index];
35845
36100
  const lineNumber = index + 1;
@@ -35870,7 +36125,7 @@ void main() {
35870
36125
  continue;
35871
36126
  }
35872
36127
  if (indent === 0) {
35873
- 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 });
35874
36129
  if (section.kind === "system") {
35875
36130
  system = section.system;
35876
36131
  } else if (section.kind === "defaults") {
@@ -35886,7 +36141,7 @@ void main() {
35886
36141
  handleSectionLine(section, indent, tokens, lineNumber);
35887
36142
  }
35888
36143
  if (!sawSchemaHeader) {
35889
- 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"');
35890
36145
  }
35891
36146
  const objects = objectNodes.map((node) => normalizeDraftObject(node, sourceSchemaVersion, diagnostics));
35892
36147
  const normalizedEvents = events.map((event) => normalizeDraftEvent(event, eventPoseNodes.get(event.id) ?? []));
@@ -35899,6 +36154,7 @@ void main() {
35899
36154
  groups,
35900
36155
  relations,
35901
36156
  events: normalizedEvents,
36157
+ trajectories,
35902
36158
  objects,
35903
36159
  diagnostics
35904
36160
  };
@@ -35928,13 +36184,13 @@ void main() {
35928
36184
  return document2;
35929
36185
  }
35930
36186
  function assertDraftSchemaHeader(tokens, line) {
35931
- 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())) {
35932
- 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);
35933
36189
  }
35934
36190
  const version = tokens[1].value.toLowerCase();
35935
- 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";
35936
36192
  }
35937
- 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) {
35938
36194
  const keyword = tokens[0]?.value.toLowerCase();
35939
36195
  switch (keyword) {
35940
36196
  case "system":
@@ -35988,6 +36244,9 @@ void main() {
35988
36244
  case "event":
35989
36245
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "event", { line, column: tokens[0].column });
35990
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);
35991
36250
  case "object":
35992
36251
  return startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes);
35993
36252
  default:
@@ -36195,6 +36454,45 @@ void main() {
36195
36454
  activePoseSeenFields: /* @__PURE__ */ new Set()
36196
36455
  };
36197
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
+ }
36198
36496
  function startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes) {
36199
36497
  if (tokens.length < 3) {
36200
36498
  throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
@@ -36254,6 +36552,9 @@ void main() {
36254
36552
  case "event":
36255
36553
  applyEventField(section, indent, tokens, line);
36256
36554
  return;
36555
+ case "trajectory":
36556
+ applyTrajectoryField(section, indent, tokens, line);
36557
+ return;
36257
36558
  case "object":
36258
36559
  applyObjectField(section, indent, tokens, line);
36259
36560
  return;
@@ -36572,6 +36873,12 @@ void main() {
36572
36873
  column: tokens[0]?.column ?? 1
36573
36874
  });
36574
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
+ }
36575
36882
  section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
36576
36883
  return;
36577
36884
  }
@@ -36614,6 +36921,13 @@ void main() {
36614
36921
  case "summary":
36615
36922
  section.event.summary = joinFieldValue(tokens, line);
36616
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;
36617
36931
  case "target":
36618
36932
  section.event.targetObjectId = joinFieldValue(tokens, line);
36619
36933
  return;
@@ -36656,6 +36970,219 @@ void main() {
36656
36970
  throw new WorldOrbitError(`Unknown event field "${tokens[0].value}"`, line, tokens[0].column);
36657
36971
  }
36658
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
+ }
36659
37186
  function parseEventPoseField(tokens, line, seenFields) {
36660
37187
  if (tokens.length < 2) {
36661
37188
  throw new WorldOrbitError("Invalid event pose field line", line, tokens[0]?.column ?? 1);
@@ -36861,6 +37388,11 @@ void main() {
36861
37388
  line,
36862
37389
  column: keyToken.column
36863
37390
  });
37391
+ } else if (spec.version === "3.0") {
37392
+ warnIfSchema30Feature(sourceSchemaVersion, diagnostics, keyToken.value, {
37393
+ line,
37394
+ column: keyToken.column
37395
+ });
36864
37396
  }
36865
37397
  index++;
36866
37398
  const valueTokens = [];
@@ -36911,6 +37443,11 @@ void main() {
36911
37443
  line,
36912
37444
  column: tokens[0].column
36913
37445
  });
37446
+ } else if (spec.version === "3.0") {
37447
+ warnIfSchema30Feature(sourceSchemaVersion, diagnostics, tokens[0].value, {
37448
+ line,
37449
+ column: tokens[0].column
37450
+ });
36914
37451
  }
36915
37452
  const field = {
36916
37453
  type: "field",
@@ -36950,6 +37487,7 @@ void main() {
36950
37487
  const tolerances = fieldMap.get("tolerance")?.map((field) => parseToleranceField(field));
36951
37488
  const typedBlocks = normalizeTypedBlocks(node.typedBlockEntries);
36952
37489
  const info2 = normalizeInfoEntries(node.infoEntries, "info");
37490
+ const trajectoryId = parseOptionalJoinedValue(fieldMap.get("trajectory")?.[0]);
36953
37491
  const object = {
36954
37492
  type: node.objectType,
36955
37493
  id: node.id,
@@ -36979,11 +37517,16 @@ void main() {
36979
37517
  object.tolerances = tolerances;
36980
37518
  if (typedBlocks && Object.keys(typedBlocks).length > 0)
36981
37519
  object.typedBlocks = typedBlocks;
37520
+ if (trajectoryId)
37521
+ object.trajectoryId = trajectoryId;
36982
37522
  if (isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
36983
- 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) {
36984
37524
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
36985
37525
  }
36986
37526
  }
37527
+ if (object.trajectoryId) {
37528
+ warnIfSchema30Feature(sourceSchemaVersion, diagnostics, `${node.id}.trajectory`, node.location);
37529
+ }
36987
37530
  return object;
36988
37531
  }
36989
37532
  function normalizeDraftEvent(event, rawPoses) {
@@ -37000,6 +37543,8 @@ void main() {
37000
37543
  return {
37001
37544
  objectId: rawPose.objectId,
37002
37545
  placement,
37546
+ trajectorySegmentId: parseOptionalJoinedValue(fieldMap.get("segment")?.[0]),
37547
+ trajectoryManeuverId: parseOptionalJoinedValue(fieldMap.get("maneuver")?.[0]),
37003
37548
  inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
37004
37549
  outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer"),
37005
37550
  epoch: parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]),
@@ -37215,6 +37760,19 @@ void main() {
37215
37760
  column: location.column
37216
37761
  });
37217
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
+ }
37218
37776
  function isSchemaOlderThan(sourceSchemaVersion, requiredVersion) {
37219
37777
  return schemaVersionRank(sourceSchemaVersion) < schemaVersionRank(requiredVersion);
37220
37778
  }
@@ -37230,8 +37788,10 @@ void main() {
37230
37788
  return 3;
37231
37789
  case "2.6":
37232
37790
  return 4;
37233
- default:
37791
+ case "3.0":
37234
37792
  return 5;
37793
+ default:
37794
+ return 6;
37235
37795
  }
37236
37796
  }
37237
37797
  function preprocessAtlasSource(source) {
@@ -37321,7 +37881,7 @@ void main() {
37321
37881
  }
37322
37882
 
37323
37883
  // packages/core/dist/atlas-edit.js
37324
- function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.6") {
37884
+ function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "3.0") {
37325
37885
  return {
37326
37886
  format: "worldorbit",
37327
37887
  version,
@@ -37352,6 +37912,7 @@ void main() {
37352
37912
  groups: [],
37353
37913
  relations: [],
37354
37914
  events: [],
37915
+ trajectories: [],
37355
37916
  objects: [],
37356
37917
  diagnostics: []
37357
37918
  };
@@ -37384,6 +37945,19 @@ void main() {
37384
37945
  paths.push({ kind: "event-pose", id: event.id, key: pose.objectId });
37385
37946
  }
37386
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
+ }
37387
37961
  for (const object of [...document2.objects].sort(compareIdLike2)) {
37388
37962
  paths.push({ kind: "object", id: object.id });
37389
37963
  }
@@ -37403,6 +37977,12 @@ void main() {
37403
37977
  return path.id ? findEvent(document2, path.id) : null;
37404
37978
  case "event-pose":
37405
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;
37406
37986
  case "object":
37407
37987
  return path.id ? findObject(document2, path.id) : null;
37408
37988
  case "viewpoint":
@@ -37454,6 +38034,24 @@ void main() {
37454
38034
  }
37455
38035
  upsertEventPose(next.events, path.id, value);
37456
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;
37457
38055
  case "object":
37458
38056
  if (!path.id) {
37459
38057
  throw new Error('Object updates require an "id" value.');
@@ -37515,6 +38113,30 @@ void main() {
37515
38113
  }
37516
38114
  }
37517
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;
37518
38140
  case "viewpoint":
37519
38141
  if (path.id) {
37520
38142
  system.viewpoints = system.viewpoints.filter((viewpoint) => viewpoint.id !== path.id);
@@ -37599,6 +38221,29 @@ void main() {
37599
38221
  };
37600
38222
  }
37601
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
+ }
37602
38247
  if (diagnostic.field && diagnostic.field in ensureSystem(document2).atlasMetadata) {
37603
38248
  return {
37604
38249
  kind: "metadata",
@@ -37636,6 +38281,19 @@ void main() {
37636
38281
  function findEventPose(document2, eventId, objectId) {
37637
38282
  return findEvent(document2, eventId)?.positions.find((pose) => pose.objectId === objectId) ?? null;
37638
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
+ }
37639
38297
  function findViewpoint(system, viewpointId) {
37640
38298
  return system?.viewpoints.find((viewpoint) => viewpoint.id === viewpointId) ?? null;
37641
38299
  }
@@ -37664,6 +38322,50 @@ void main() {
37664
38322
  }
37665
38323
  event.positions[index] = value;
37666
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
+ }
37667
38369
  function compareIdLike2(left, right) {
37668
38370
  return left.id.localeCompare(right.id);
37669
38371
  }
@@ -37672,10 +38374,11 @@ void main() {
37672
38374
  }
37673
38375
 
37674
38376
  // packages/core/dist/load.js
37675
- 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;
37676
38378
  var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
37677
38379
  var ATLAS_SCHEMA_25_PATTERN = /^schema\s+2\.5$/i;
37678
38380
  var ATLAS_SCHEMA_26_PATTERN = /^schema\s+2\.6$/i;
38381
+ var ATLAS_SCHEMA_30_PATTERN = /^schema\s+3(?:\.0)?$/i;
37679
38382
  var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
37680
38383
  function detectWorldOrbitSchemaVersion(source) {
37681
38384
  for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
@@ -37695,6 +38398,9 @@ void main() {
37695
38398
  if (ATLAS_SCHEMA_26_PATTERN.test(trimmed)) {
37696
38399
  return "2.6";
37697
38400
  }
38401
+ if (ATLAS_SCHEMA_30_PATTERN.test(trimmed)) {
38402
+ return "3.0";
38403
+ }
37698
38404
  if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
37699
38405
  return "2.0";
37700
38406
  }
@@ -37755,7 +38461,7 @@ void main() {
37755
38461
  }
37756
38462
  function loadWorldOrbitSourceWithDiagnostics(source) {
37757
38463
  const schemaVersion = detectWorldOrbitSchemaVersion(source);
37758
- 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") {
37759
38465
  return loadAtlasSourceWithDiagnostics(source, schemaVersion);
37760
38466
  }
37761
38467
  let ast;
@@ -38653,6 +39359,8 @@ void main() {
38653
39359
  case "comet":
38654
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" />
38655
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" />`;
38656
39364
  case "structure":
38657
39365
  return `<polygon points="${diamondPoints(x, y, radius)}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
38658
39366
  case "phenomenon": {
@@ -38786,6 +39494,8 @@ void main() {
38786
39494
  return { fill: "#9ce7ff", stroke: "#e7fbff" };
38787
39495
  case "ring":
38788
39496
  return { fill: "#e59f7d", stroke: "#fff0d3" };
39497
+ case "craft":
39498
+ return { fill: "#ffb47f", stroke: "#fff0d3" };
38789
39499
  case "structure":
38790
39500
  return { fill: theme.accentStrong, stroke: "#fff2ea" };
38791
39501
  case "phenomenon":
@@ -39051,6 +39761,8 @@ void main() {
39051
39761
  return "#a7a5b8";
39052
39762
  case "comet":
39053
39763
  return "#9ce7ff";
39764
+ case "craft":
39765
+ return "#ffb47f";
39054
39766
  case "structure":
39055
39767
  return "#ff7f5f";
39056
39768
  case "phenomenon":
@@ -39709,6 +40421,8 @@ void main() {
39709
40421
  return "#b8926a";
39710
40422
  case "ring":
39711
40423
  return "#cdbf9a";
40424
+ case "craft":
40425
+ return "#ffce8a";
39712
40426
  case "structure":
39713
40427
  return "#ffce8a";
39714
40428
  case "phenomenon":
@@ -40965,7 +41679,7 @@ void main() {
40965
41679
  tooltipRoot.hidden = false;
40966
41680
  tooltipRoot.dataset.mode = resolved.mode;
40967
41681
  tooltipRoot.classList.toggle("is-pinned", resolved.mode === "pinned");
40968
- tooltipRoot.style.pointerEvents = "auto";
41682
+ tooltipRoot.style.pointerEvents = resolved.mode === "pinned" ? "auto" : "none";
40969
41683
  tooltipRoot.style.visibility = "hidden";
40970
41684
  renderTooltipContent(tooltipRoot, tooltipDetails, resolved.mode);
40971
41685
  positionTooltip(tooltipRoot, details.renderObject);
@@ -41558,7 +42272,11 @@ void main() {
41558
42272
  if (!(target instanceof Element)) {
41559
42273
  return null;
41560
42274
  }
41561
- 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;
41562
42280
  }
41563
42281
  function ensureBrowserEnvironment(container) {
41564
42282
  if (typeof window === "undefined" || typeof document === "undefined") {
@@ -41690,7 +42408,7 @@ void main() {
41690
42408
  backdrop-filter: blur(12px);
41691
42409
  font: 500 13px/1.5 "Segoe UI Variable", "Segoe UI", sans-serif;
41692
42410
  }
41693
- .wo-viewer-tooltip-root[data-mode="hover"] { pointer-events: auto; }
42411
+ .wo-viewer-tooltip-root[data-mode="hover"] { pointer-events: none; }
41694
42412
  .wo-viewer-tooltip-root[data-mode="pinned"] { pointer-events: auto; }
41695
42413
  .wo-tooltip-card { display: grid; gap: 10px; }
41696
42414
  .wo-tooltip-head { display: grid; grid-template-columns: 52px minmax(0, 1fr); gap: 12px; align-items: center; }