worldorbit 3.2.2 → 5.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 (82) hide show
  1. package/README.md +543 -543
  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 +461 -17
  5. package/dist/browser/core/dist/draft.d.ts +2 -1
  6. package/dist/browser/core/dist/draft.js +26 -4
  7. package/dist/browser/core/dist/format.js +126 -5
  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 +12 -2
  11. package/dist/browser/core/dist/normalize.js +1 -0
  12. package/dist/browser/core/dist/scene.js +226 -5
  13. package/dist/browser/core/dist/schema.js +11 -1
  14. package/dist/browser/core/dist/solver.d.ts +33 -0
  15. package/dist/browser/core/dist/solver.js +99 -0
  16. package/dist/browser/core/dist/spatial-scene.js +56 -0
  17. package/dist/browser/core/dist/types.d.ts +130 -4
  18. package/dist/browser/editor/dist/editor.js +844 -719
  19. package/dist/browser/editor/dist/types.d.ts +2 -1
  20. package/dist/browser/viewer/dist/minimap.js +9 -7
  21. package/dist/browser/viewer/dist/render.js +78 -18
  22. package/dist/browser/viewer/dist/runtime-3d.js +2 -0
  23. package/dist/browser/viewer/dist/theme.js +1 -0
  24. package/dist/browser/viewer/dist/types.d.ts +7 -0
  25. package/dist/browser/viewer/dist/viewer.js +34 -3
  26. package/dist/obsidian-plugin/README.md +141 -124
  27. package/dist/obsidian-plugin/main.js +82 -68
  28. package/dist/unpkg/core/dist/atlas-edit.js +146 -1
  29. package/dist/unpkg/core/dist/atlas-validate.js +105 -10
  30. package/dist/unpkg/core/dist/draft-parse.js +461 -17
  31. package/dist/unpkg/core/dist/draft.d.ts +2 -1
  32. package/dist/unpkg/core/dist/draft.js +26 -4
  33. package/dist/unpkg/core/dist/format.js +126 -5
  34. package/dist/unpkg/core/dist/index.d.ts +1 -0
  35. package/dist/unpkg/core/dist/index.js +1 -0
  36. package/dist/unpkg/core/dist/load.js +12 -2
  37. package/dist/unpkg/core/dist/normalize.js +1 -0
  38. package/dist/unpkg/core/dist/scene.js +226 -5
  39. package/dist/unpkg/core/dist/schema.js +11 -1
  40. package/dist/unpkg/core/dist/solver.d.ts +33 -0
  41. package/dist/unpkg/core/dist/solver.js +99 -0
  42. package/dist/unpkg/core/dist/spatial-scene.js +56 -0
  43. package/dist/unpkg/core/dist/types.d.ts +130 -4
  44. package/dist/unpkg/editor/dist/editor.js +844 -719
  45. package/dist/unpkg/editor/dist/types.d.ts +2 -1
  46. package/dist/unpkg/viewer/dist/minimap.js +9 -7
  47. package/dist/unpkg/viewer/dist/render.js +78 -18
  48. package/dist/unpkg/viewer/dist/runtime-3d.js +2 -0
  49. package/dist/unpkg/viewer/dist/theme.js +1 -0
  50. package/dist/unpkg/viewer/dist/types.d.ts +7 -0
  51. package/dist/unpkg/viewer/dist/viewer.js +34 -3
  52. package/dist/unpkg/worldorbit-core.min.js +12 -12
  53. package/dist/unpkg/worldorbit-editor.min.js +381 -340
  54. package/dist/unpkg/worldorbit-markdown.min.js +47 -33
  55. package/dist/unpkg/worldorbit-viewer.min.js +238 -224
  56. package/dist/unpkg/worldorbit.js +1218 -46
  57. package/dist/unpkg/worldorbit.min.js +242 -228
  58. package/package.json +5 -1
  59. package/packages/core/dist/atlas-edit.js +146 -1
  60. package/packages/core/dist/atlas-validate.js +105 -10
  61. package/packages/core/dist/draft-parse.js +461 -17
  62. package/packages/core/dist/draft.d.ts +2 -1
  63. package/packages/core/dist/draft.js +26 -4
  64. package/packages/core/dist/format.js +126 -5
  65. package/packages/core/dist/index.d.ts +1 -0
  66. package/packages/core/dist/index.js +1 -0
  67. package/packages/core/dist/load.js +12 -2
  68. package/packages/core/dist/normalize.js +1 -0
  69. package/packages/core/dist/scene.js +226 -5
  70. package/packages/core/dist/schema.js +11 -1
  71. package/packages/core/dist/solver.d.ts +33 -0
  72. package/packages/core/dist/solver.js +99 -0
  73. package/packages/core/dist/spatial-scene.js +56 -0
  74. package/packages/core/dist/types.d.ts +130 -4
  75. package/packages/editor/dist/editor.js +844 -719
  76. package/packages/editor/dist/types.d.ts +2 -1
  77. package/packages/viewer/dist/minimap.js +9 -7
  78. package/packages/viewer/dist/render.js +78 -18
  79. package/packages/viewer/dist/runtime-3d.js +2 -0
  80. package/packages/viewer/dist/theme.js +1 -0
  81. package/packages/viewer/dist/types.d.ts +7 -0
  82. package/packages/viewer/dist/viewer.js +34 -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
  }
@@ -31989,7 +32001,8 @@ void main() {
31989
32001
  const labels = createSceneLabels(objects, width, height, scaleModel.labelMultiplier);
31990
32002
  const relations = createSceneRelations(document2, objects);
31991
32003
  const events = createSceneEvents(document2.events ?? [], objects, activeEventId);
31992
- const layers = createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels);
32004
+ const trajectories = createSceneTrajectories(document2, objects, events, options);
32005
+ const layers = createSceneLayers(orbitVisuals, trajectories, relations, events, leaders, objects, labels);
31993
32006
  const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, scaleModel.labelMultiplier);
31994
32007
  const semanticGroups = createSceneSemanticGroups(document2, objects);
31995
32008
  const viewpoints = createSceneViewpoints(document2, schemaProjection, frame.preset, relationships, objectMap);
@@ -32029,6 +32042,7 @@ void main() {
32029
32042
  viewpoints,
32030
32043
  events,
32031
32044
  activeEventId,
32045
+ trajectories,
32032
32046
  objects,
32033
32047
  orbitVisuals,
32034
32048
  relations,
@@ -32304,7 +32318,7 @@ void main() {
32304
32318
  anchorX,
32305
32319
  anchorY,
32306
32320
  label: object.id,
32307
- secondaryLabel: object.type === "structure" ? String(object.properties.kind ?? object.type) : object.type,
32321
+ secondaryLabel: object.type === "structure" || object.type === "craft" ? String(object.properties.kind ?? object.type) : object.type,
32308
32322
  fillColor: customColorFor(object.properties.color),
32309
32323
  imageHref: typeof object.properties.image === "string" && object.properties.image.trim() ? object.properties.image : void 0,
32310
32324
  hidden: object.properties.hidden === true
@@ -32398,6 +32412,7 @@ void main() {
32398
32412
  case "asteroid":
32399
32413
  case "comet":
32400
32414
  return 4;
32415
+ case "craft":
32401
32416
  case "structure":
32402
32417
  case "phenomenon":
32403
32418
  return 5;
@@ -32422,7 +32437,7 @@ void main() {
32422
32437
  const oppositeVertical = vertical === "below" ? "above" : "below";
32423
32438
  const horizontal = defaultHorizontalDirection(object, parent, sceneWidth);
32424
32439
  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";
32440
+ 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
32441
  return preferHorizontal ? [horizontal, vertical, oppositeHorizontal, oppositeVertical] : [vertical, horizontal, oppositeVertical, oppositeHorizontal];
32427
32442
  }
32428
32443
  function defaultVerticalDirection(object, parent, sceneHeight) {
@@ -32484,7 +32499,7 @@ void main() {
32484
32499
  }
32485
32500
  }
32486
32501
  }
32487
- function createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels) {
32502
+ function createSceneLayers(orbitVisuals, trajectories, relations, events, leaders, objects, labels) {
32488
32503
  const backOrbitIds = orbitVisuals.filter((visual) => !visual.hidden && Boolean(visual.backArcPath)).map((visual) => visual.renderId);
32489
32504
  const frontOrbitIds = orbitVisuals.filter((visual) => !visual.hidden).map((visual) => visual.renderId);
32490
32505
  return [
@@ -32495,6 +32510,13 @@ void main() {
32495
32510
  },
32496
32511
  { id: "orbits-back", renderIds: backOrbitIds },
32497
32512
  { id: "orbits-front", renderIds: frontOrbitIds },
32513
+ {
32514
+ id: "trajectories",
32515
+ renderIds: trajectories.filter((trajectory) => !trajectory.hidden).flatMap((trajectory) => [
32516
+ trajectory.renderId,
32517
+ ...trajectory.waypoints.filter((waypoint) => !waypoint.hidden).map((waypoint) => waypoint.renderId)
32518
+ ])
32519
+ },
32498
32520
  {
32499
32521
  id: "relations",
32500
32522
  renderIds: relations.filter((relation) => !relation.hidden).map((relation) => relation.renderId)
@@ -32620,6 +32642,192 @@ void main() {
32620
32642
  };
32621
32643
  }).sort((left, right) => left.event.id.localeCompare(right.event.id));
32622
32644
  }
32645
+ function createSceneTrajectories(document2, objects, events, options) {
32646
+ const objectMap = new Map(objects.map((object) => [object.objectId, object]));
32647
+ return document2.trajectories.map((trajectory) => createSceneTrajectory(trajectory, objectMap, events, options)).sort((left, right) => left.trajectoryId.localeCompare(right.trajectoryId));
32648
+ }
32649
+ function createSceneTrajectory(trajectory, objectMap, events, options) {
32650
+ const craftObject = trajectory.craftObjectId ? objectMap.get(trajectory.craftObjectId) ?? null : null;
32651
+ const mode = resolveTrajectoryMode(trajectory, options);
32652
+ const stroke = trajectory.stroke ?? trajectory.color ?? craftObject?.fillColor ?? null;
32653
+ const strokeWidth = trajectory.strokeWidth ?? 2.4;
32654
+ const showWaypoints = options.showTrajectoryWaypoints ?? trajectory.showWaypoints ?? true;
32655
+ const labelMode = trajectory.labelMode ?? (options.showTrajectoryLabels === false ? "hidden" : "waypoint");
32656
+ const pathParts = [];
32657
+ const waypoints = [];
32658
+ const objectIds = /* @__PURE__ */ new Set();
32659
+ let lastAnchor = craftObject;
32660
+ if (craftObject) {
32661
+ objectIds.add(craftObject.objectId);
32662
+ }
32663
+ trajectory.segments.forEach((segment, index) => {
32664
+ const geometry = buildTrajectorySegmentGeometry(trajectory, segment, index, mode, objectMap, lastAnchor, showWaypoints);
32665
+ if (geometry.path) {
32666
+ pathParts.push(geometry.path);
32667
+ }
32668
+ geometry.objectIds.forEach((objectId) => objectIds.add(objectId));
32669
+ waypoints.push(...geometry.waypoints);
32670
+ lastAnchor = geometry.lastAnchor ?? lastAnchor;
32671
+ });
32672
+ for (const event of events.filter((entry) => entry.event.trajectoryId === trajectory.id)) {
32673
+ const eventObject = event.targetObjectId ? objectMap.get(event.targetObjectId) ?? null : event.objectIds.map((objectId) => objectMap.get(objectId) ?? null).find(Boolean) ?? null;
32674
+ if (!eventObject) {
32675
+ continue;
32676
+ }
32677
+ waypoints.push({
32678
+ renderId: `${createRenderId(`${trajectory.id}-${event.eventId}`)}-waypoint`,
32679
+ trajectoryId: trajectory.id,
32680
+ segmentId: null,
32681
+ maneuverId: null,
32682
+ objectId: eventObject.objectId,
32683
+ x: eventObject.x,
32684
+ y: eventObject.y,
32685
+ label: event.event.label ?? event.event.id,
32686
+ dateLabel: event.event.epoch ?? null,
32687
+ hidden: trajectory.hidden || event.hidden || !showWaypoints
32688
+ });
32689
+ objectIds.add(eventObject.objectId);
32690
+ }
32691
+ return {
32692
+ renderId: `${createRenderId(trajectory.id)}-trajectory`,
32693
+ trajectoryId: trajectory.id,
32694
+ trajectory,
32695
+ craftObjectId: trajectory.craftObjectId,
32696
+ mode,
32697
+ path: pathParts.join(" "),
32698
+ stroke,
32699
+ strokeWidth,
32700
+ marker: trajectory.marker ?? "arrow",
32701
+ labelMode,
32702
+ showWaypoints,
32703
+ objectIds: [...objectIds],
32704
+ waypoints,
32705
+ hidden: trajectory.hidden || pathParts.length === 0
32706
+ };
32707
+ }
32708
+ function buildTrajectorySegmentGeometry(trajectory, segment, segmentIndex, mode, objectMap, fallbackStart, showWaypoints) {
32709
+ const start = resolveTrajectoryObject(segment.fromObjectId, objectMap) ?? fallbackStart;
32710
+ const assist = resolveTrajectoryObject(segment.assist?.objectId ?? segment.aroundObjectId ?? null, objectMap);
32711
+ const end = resolveTrajectoryObject(segment.toObjectId, objectMap) ?? resolveTrajectoryObject(segment.aroundObjectId, objectMap) ?? assist ?? start;
32712
+ if (!start || !end) {
32713
+ return {
32714
+ path: "",
32715
+ objectIds: [],
32716
+ waypoints: [],
32717
+ lastAnchor: fallbackStart
32718
+ };
32719
+ }
32720
+ const objectIds = [start.objectId, end.objectId];
32721
+ if (assist) {
32722
+ objectIds.push(assist.objectId);
32723
+ }
32724
+ const hidden = trajectory.hidden || segment.renderHidden === true || start.hidden || end.hidden;
32725
+ const waypoints = [];
32726
+ const label = segment.waypointLabel ?? segment.label ?? humanizeIdentifier(segment.id);
32727
+ const dateLabel = segment.waypointDate ?? segment.epoch ?? null;
32728
+ if (showWaypoints) {
32729
+ if (segmentIndex === 0) {
32730
+ waypoints.push(createTrajectoryWaypoint(trajectory.id, segment.id, null, start, start.label, null, hidden));
32731
+ }
32732
+ if (assist && assist.objectId !== start.objectId && assist.objectId !== end.objectId) {
32733
+ waypoints.push(createTrajectoryWaypoint(trajectory.id, segment.id, null, assist, label, dateLabel, hidden));
32734
+ }
32735
+ waypoints.push(createTrajectoryWaypoint(trajectory.id, segment.id, null, end, label, dateLabel, hidden));
32736
+ }
32737
+ const samples = mode === "solver" ? sampleQuadraticPoints(start, resolveTrajectoryControlPoint(start, end, assist, segmentIndex), end, Math.max(10, Math.round((segment.sampleDensity ?? 1) * 14))) : null;
32738
+ const control = resolveTrajectoryControlPoint(start, end, assist, segmentIndex);
32739
+ const path = mode === "solver" ? pointsToPath(samples ?? [start, end]) : `M ${formatPathNumber(start.x)} ${formatPathNumber(start.y)} Q ${formatPathNumber(control.x)} ${formatPathNumber(control.y)} ${formatPathNumber(end.x)} ${formatPathNumber(end.y)}`;
32740
+ segment.maneuvers.forEach((maneuver, maneuverIndex) => {
32741
+ if (!showWaypoints) {
32742
+ return;
32743
+ }
32744
+ const point = samplePointOnQuadratic(start, control, end, (maneuverIndex + 1) / (segment.maneuvers.length + 1));
32745
+ waypoints.push({
32746
+ renderId: `${createRenderId(`${trajectory.id}-${segment.id}-${maneuver.id}`)}-waypoint`,
32747
+ trajectoryId: trajectory.id,
32748
+ segmentId: segment.id,
32749
+ maneuverId: maneuver.id,
32750
+ objectId: null,
32751
+ x: point.x,
32752
+ y: point.y,
32753
+ label: maneuver.label ?? maneuver.kind,
32754
+ dateLabel: maneuver.epoch ?? null,
32755
+ hidden
32756
+ });
32757
+ });
32758
+ return {
32759
+ path,
32760
+ objectIds: [...new Set(objectIds)],
32761
+ waypoints,
32762
+ lastAnchor: end
32763
+ };
32764
+ }
32765
+ function createTrajectoryWaypoint(trajectoryId, segmentId, maneuverId, object, label, dateLabel, hidden) {
32766
+ return {
32767
+ renderId: `${createRenderId(`${trajectoryId}-${segmentId ?? object.objectId}-${maneuverId ?? object.objectId}`)}-waypoint`,
32768
+ trajectoryId,
32769
+ segmentId,
32770
+ maneuverId,
32771
+ objectId: object.objectId,
32772
+ x: object.x,
32773
+ y: object.y,
32774
+ label,
32775
+ dateLabel,
32776
+ hidden
32777
+ };
32778
+ }
32779
+ function resolveTrajectoryMode(trajectory, options) {
32780
+ const requested = options.trajectoryMode ?? trajectory.renderMode ?? "auto";
32781
+ if (requested !== "auto") {
32782
+ return requested;
32783
+ }
32784
+ const segmentCount = trajectory.segments.filter((segment) => segment.fromObjectId || segment.toObjectId || segment.assist?.objectId || segment.aroundObjectId).length;
32785
+ return segmentCount > 0 ? "solver" : "illustrative";
32786
+ }
32787
+ function resolveTrajectoryObject(objectId, objectMap) {
32788
+ return objectId ? objectMap.get(objectId) ?? null : null;
32789
+ }
32790
+ function resolveTrajectoryControlPoint(start, end, assist, segmentIndex) {
32791
+ if (assist) {
32792
+ return {
32793
+ x: assist.x,
32794
+ y: assist.y
32795
+ };
32796
+ }
32797
+ const dx = end.x - start.x;
32798
+ const dy = end.y - start.y;
32799
+ const length = Math.max(Math.hypot(dx, dy), 1);
32800
+ const offset = Math.min(Math.max(length * 0.18, 26), 120) * (segmentIndex % 2 === 0 ? 1 : -1);
32801
+ const normalX = -dy / length;
32802
+ const normalY = dx / length;
32803
+ return {
32804
+ x: (start.x + end.x) / 2 + normalX * offset,
32805
+ y: (start.y + end.y) / 2 + normalY * offset
32806
+ };
32807
+ }
32808
+ function sampleQuadraticPoints(start, control, end, count) {
32809
+ const samples = [];
32810
+ for (let index = 0; index <= count; index += 1) {
32811
+ samples.push(samplePointOnQuadratic(start, control, end, index / count));
32812
+ }
32813
+ return samples;
32814
+ }
32815
+ function samplePointOnQuadratic(start, control, end, t) {
32816
+ const inverse = 1 - t;
32817
+ return {
32818
+ x: inverse * inverse * start.x + 2 * inverse * t * control.x + t * t * end.x,
32819
+ y: inverse * inverse * start.y + 2 * inverse * t * control.y + t * t * end.y
32820
+ };
32821
+ }
32822
+ function pointsToPath(points) {
32823
+ if (points.length === 0) {
32824
+ return "";
32825
+ }
32826
+ return points.map((point, index) => `${index === 0 ? "M" : "L"} ${formatPathNumber(point.x)} ${formatPathNumber(point.y)}`).join(" ");
32827
+ }
32828
+ function formatPathNumber(value) {
32829
+ return Number.isFinite(value) ? value.toFixed(2) : "0";
32830
+ }
32623
32831
  function createSceneViewpoints(document2, projection, preset, relationships, objectMap) {
32624
32832
  const generatedOverview = createGeneratedOverviewViewpoint(document2, projection, preset);
32625
32833
  const drafts = /* @__PURE__ */ new Map();
@@ -32879,14 +33087,14 @@ void main() {
32879
33087
  next["orbits-front"] = enabled;
32880
33088
  continue;
32881
33089
  }
32882
- if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "events" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
33090
+ if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "events" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata" || rawLayer === "trajectories") {
32883
33091
  next[rawLayer] = enabled;
32884
33092
  }
32885
33093
  }
32886
33094
  return next;
32887
33095
  }
32888
33096
  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");
33097
+ 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
33098
  }
32891
33099
  function parseViewpointGroups(value, document2, relationships, objectMap) {
32892
33100
  return splitListValue(value).map((entry) => {
@@ -33507,6 +33715,8 @@ void main() {
33507
33715
  return clampNumber(6 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
33508
33716
  case "ring":
33509
33717
  return clampNumber(5 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
33718
+ case "craft":
33719
+ return clampNumber(5 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
33510
33720
  case "structure":
33511
33721
  return clampNumber(6 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
33512
33722
  case "phenomenon":
@@ -33520,6 +33730,8 @@ void main() {
33520
33730
  return radius * 2.4;
33521
33731
  case "phenomenon":
33522
33732
  return radius * 1.25;
33733
+ case "craft":
33734
+ return radius + 1.5;
33523
33735
  case "structure":
33524
33736
  return radius + 2;
33525
33737
  default:
@@ -33725,6 +33937,7 @@ void main() {
33725
33937
  const spatialObjects = scene.objects.map((entry) => createSpatialObject(entry, scene, sceneCenter, objectMap, orbitMap, scaleModel, positionCache, minimumMotionMetric));
33726
33938
  const spatialObjectMap = new Map(spatialObjects.map((object) => [object.objectId, object]));
33727
33939
  const spatialOrbits = scene.orbitVisuals.map((orbit) => createSpatialOrbit(orbit, spatialObjectMap, minimumMotionMetric, scene.activeEventId !== null));
33940
+ const spatialTrajectories = scene.trajectories.map((trajectory) => createSpatialTrajectory(trajectory, spatialObjectMap));
33728
33941
  const focusTargets = spatialObjects.map((object) => ({
33729
33942
  objectId: object.objectId,
33730
33943
  center: { ...object.position },
@@ -33754,6 +33967,7 @@ void main() {
33754
33967
  timeFrozen: scene.activeEventId !== null,
33755
33968
  objects: spatialObjects,
33756
33969
  orbits: spatialOrbits,
33970
+ trajectories: spatialTrajectories,
33757
33971
  focusTargets
33758
33972
  };
33759
33973
  }
@@ -33801,6 +34015,39 @@ void main() {
33801
34015
  motion: owner?.motion ?? createMotionModel(orbit.object, orbit, minimumMotionMetric, frozen)
33802
34016
  };
33803
34017
  }
34018
+ function createSpatialTrajectory(trajectory, objectMap) {
34019
+ const samples = samplePathPoints(trajectory.path).map((point) => ({
34020
+ x: point.x,
34021
+ y: 0,
34022
+ z: point.y
34023
+ }));
34024
+ return {
34025
+ trajectoryId: trajectory.trajectoryId,
34026
+ trajectory: trajectory.trajectory,
34027
+ craftObjectId: trajectory.craftObjectId,
34028
+ mode: trajectory.mode,
34029
+ stroke: trajectory.stroke,
34030
+ strokeWidth: trajectory.strokeWidth,
34031
+ marker: trajectory.marker,
34032
+ labelMode: trajectory.labelMode,
34033
+ showWaypoints: trajectory.showWaypoints,
34034
+ samples,
34035
+ waypoints: trajectory.waypoints.map((waypoint) => {
34036
+ const object = waypoint.objectId ? objectMap.get(waypoint.objectId) ?? null : null;
34037
+ return {
34038
+ trajectoryId: waypoint.trajectoryId,
34039
+ segmentId: waypoint.segmentId,
34040
+ maneuverId: waypoint.maneuverId,
34041
+ objectId: waypoint.objectId,
34042
+ position: object ? { ...object.position } : { x: waypoint.x, y: 0, z: waypoint.y },
34043
+ label: waypoint.label,
34044
+ dateLabel: waypoint.dateLabel,
34045
+ hidden: waypoint.hidden
34046
+ };
34047
+ }),
34048
+ hidden: trajectory.hidden
34049
+ };
34050
+ }
33804
34051
  function resolveSpatialObjectPosition(entry, scene, sceneCenter, objectMap, orbitMap, cache) {
33805
34052
  const cached = cache.get(entry.objectId);
33806
34053
  if (cached) {
@@ -34084,6 +34331,54 @@ void main() {
34084
34331
  function degreesToRadians2(value) {
34085
34332
  return value * Math.PI / 180;
34086
34333
  }
34334
+ function samplePathPoints(path) {
34335
+ const matches = [...path.matchAll(/[MLQ]\s*(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)(?:\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?))?(?:\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?))?/g)];
34336
+ if (matches.length === 0) {
34337
+ return [];
34338
+ }
34339
+ const points = [];
34340
+ for (const match of matches) {
34341
+ const command = match[0][0];
34342
+ if (command === "M" || command === "L") {
34343
+ points.push({ x: Number(match[1]), y: Number(match[2]) });
34344
+ continue;
34345
+ }
34346
+ if (command === "Q") {
34347
+ points.push({ x: Number(match[1]), y: Number(match[2]) });
34348
+ points.push({ x: Number(match[5]), y: Number(match[6]) });
34349
+ }
34350
+ }
34351
+ return points;
34352
+ }
34353
+
34354
+ // packages/core/dist/solver.js
34355
+ function createTrajectorySolverSnapshot(trajectory) {
34356
+ return {
34357
+ trajectoryId: trajectory.id,
34358
+ craftObjectId: trajectory.craftObjectId,
34359
+ segments: trajectory.segments.map((segment) => ({
34360
+ segmentId: segment.id,
34361
+ kind: segment.kind,
34362
+ fromObjectId: segment.fromObjectId,
34363
+ toObjectId: segment.toObjectId,
34364
+ aroundObjectId: segment.aroundObjectId,
34365
+ assistObjectId: segment.assist?.objectId ?? null,
34366
+ duration: segment.duration ?? null,
34367
+ deltaV: segment.deltaV ?? null
34368
+ })),
34369
+ maneuvers: trajectory.segments.flatMap((segment) => segment.maneuvers.map((maneuver) => mapManeuver(segment.id, maneuver)))
34370
+ };
34371
+ }
34372
+ function mapManeuver(segmentId, maneuver) {
34373
+ return {
34374
+ segmentId,
34375
+ maneuverId: maneuver.id,
34376
+ kind: maneuver.kind,
34377
+ epoch: maneuver.epoch,
34378
+ deltaV: maneuver.deltaV ?? null,
34379
+ duration: maneuver.duration ?? null
34380
+ };
34381
+ }
34087
34382
 
34088
34383
  // packages/core/dist/draft.js
34089
34384
  function upgradeDocumentToV2(document2, options = {}) {
@@ -34103,15 +34398,16 @@ void main() {
34103
34398
  }
34104
34399
  return {
34105
34400
  format: "worldorbit",
34106
- version: "2.6",
34107
- schemaVersion: "2.6",
34401
+ version: "3.1",
34402
+ schemaVersion: "3.1",
34108
34403
  sourceVersion: document2.version,
34109
34404
  theme: document2.theme ?? null,
34110
34405
  system,
34111
34406
  groups: structuredClone(document2.groups ?? []),
34112
34407
  relations: structuredClone(document2.relations ?? []),
34113
34408
  events: structuredClone(document2.events ?? []),
34114
- objects: document2.objects.map(cloneWorldOrbitObject),
34409
+ trajectories: structuredClone(document2.trajectories ?? []),
34410
+ objects: document2.objects.map(cloneWorldOrbitObject).map(normalizeLegacyCraftObject),
34115
34411
  diagnostics
34116
34412
  };
34117
34413
  }
@@ -34140,6 +34436,7 @@ void main() {
34140
34436
  groups: structuredClone(document2.groups ?? []),
34141
34437
  relations: structuredClone(document2.relations ?? []),
34142
34438
  events: document2.events.map(cloneWorldOrbitEvent),
34439
+ trajectories: document2.trajectories.map(cloneWorldOrbitTrajectory),
34143
34440
  objects
34144
34441
  };
34145
34442
  }
@@ -34287,6 +34584,7 @@ void main() {
34287
34584
  function cloneWorldOrbitObject(object) {
34288
34585
  return {
34289
34586
  ...object,
34587
+ trajectoryId: object.trajectoryId ?? null,
34290
34588
  groups: object.groups ? [...object.groups] : void 0,
34291
34589
  resonance: object.resonance ? { ...object.resonance } : object.resonance,
34292
34590
  renderHints: object.renderHints ? { ...object.renderHints } : object.renderHints,
@@ -34306,6 +34604,7 @@ void main() {
34306
34604
  function cloneWorldOrbitEvent(event) {
34307
34605
  return {
34308
34606
  ...event,
34607
+ trajectoryId: event.trajectoryId ?? null,
34309
34608
  participantObjectIds: [...event.participantObjectIds],
34310
34609
  tags: [...event.tags],
34311
34610
  positions: event.positions.map(cloneWorldOrbitEventPose)
@@ -34315,12 +34614,30 @@ void main() {
34315
34614
  return {
34316
34615
  objectId: pose.objectId,
34317
34616
  placement: clonePlacement(pose.placement),
34617
+ trajectorySegmentId: pose.trajectorySegmentId ?? null,
34618
+ trajectoryManeuverId: pose.trajectoryManeuverId ?? null,
34318
34619
  inner: pose.inner ? { ...pose.inner } : void 0,
34319
34620
  outer: pose.outer ? { ...pose.outer } : void 0,
34320
34621
  epoch: pose.epoch ?? null,
34321
34622
  referencePlane: pose.referencePlane ?? null
34322
34623
  };
34323
34624
  }
34625
+ function cloneWorldOrbitTrajectory(trajectory) {
34626
+ return structuredClone(trajectory);
34627
+ }
34628
+ function normalizeLegacyCraftObject(object) {
34629
+ if (object.type !== "structure") {
34630
+ return object;
34631
+ }
34632
+ const kind = typeof object.properties.kind === "string" ? object.properties.kind.toLowerCase() : "";
34633
+ if (!["ship", "probe", "station"].includes(kind)) {
34634
+ return object;
34635
+ }
34636
+ return {
34637
+ ...object,
34638
+ type: "craft"
34639
+ };
34640
+ }
34324
34641
  function clonePlacement(placement) {
34325
34642
  return placement ? structuredClone(placement) : null;
34326
34643
  }
@@ -34508,7 +34825,7 @@ void main() {
34508
34825
  if (orbitFront !== void 0 || orbitBack !== void 0) {
34509
34826
  tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
34510
34827
  }
34511
- for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
34828
+ for (const key of ["background", "guides", "relations", "events", "objects", "trajectories", "labels", "metadata"]) {
34512
34829
  if (layers[key] !== void 0) {
34513
34830
  tokens.push(layers[key] ? key : `-${key}`);
34514
34831
  }
@@ -34561,7 +34878,7 @@ void main() {
34561
34878
  ];
34562
34879
  function formatDocument(document2, options = {}) {
34563
34880
  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";
34881
+ const useDraft = schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.6" || schema === "3.0" || schema === "3.1" || schema === "2.0-draft" || document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" || document2.version === "3.0" || document2.version === "3.1" || document2.version === "2.6" || document2.version === "2.0-draft";
34565
34882
  if (useDraft) {
34566
34883
  if (schema === "2.0-draft") {
34567
34884
  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 +34888,12 @@ void main() {
34571
34888
  } : upgradeDocumentToDraftV2(document2);
34572
34889
  return formatDraftDocument(legacyDraftDocument);
34573
34890
  }
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" ? {
34891
+ const atlasDocument = document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" || document2.version === "2.6" || document2.version === "3.0" || document2.version === "3.1" ? document2 : document2.version === "2.0-draft" ? {
34575
34892
  ...document2,
34576
34893
  version: "2.0",
34577
34894
  schemaVersion: "2.0"
34578
34895
  } : upgradeDocumentToV2(document2);
34579
- if ((schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.6") && atlasDocument.version !== schema) {
34896
+ if ((schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.6" || schema === "3.0" || schema === "3.1") && atlasDocument.version !== schema) {
34580
34897
  return formatAtlasDocument({
34581
34898
  ...atlasDocument,
34582
34899
  version: schema,
@@ -34616,6 +34933,10 @@ void main() {
34616
34933
  lines.push("");
34617
34934
  lines.push(...formatAtlasEvent(event));
34618
34935
  }
34936
+ for (const trajectory of [...document2.trajectories].sort(compareIdLike)) {
34937
+ lines.push("");
34938
+ lines.push(...formatAtlasTrajectory(trajectory));
34939
+ }
34619
34940
  const sortedObjects = [...document2.objects].sort(compareObjects);
34620
34941
  if (sortedObjects.length > 0 && lines.at(-1) !== "") {
34621
34942
  lines.push("");
@@ -34799,6 +35120,9 @@ void main() {
34799
35120
  if (object.groups?.length) {
34800
35121
  lines.push(`groups ${object.groups.join(" ")}`);
34801
35122
  }
35123
+ if (object.trajectoryId) {
35124
+ lines.push(`trajectory ${object.trajectoryId}`);
35125
+ }
34802
35126
  if (object.epoch) {
34803
35127
  lines.push(`epoch ${quoteIfNeeded(object.epoch)}`);
34804
35128
  }
@@ -34959,6 +35283,9 @@ void main() {
34959
35283
  if (event.summary) {
34960
35284
  lines.push(` summary ${quoteIfNeeded(event.summary)}`);
34961
35285
  }
35286
+ if (event.trajectoryId) {
35287
+ lines.push(` trajectory ${event.trajectoryId}`);
35288
+ }
34962
35289
  if (event.targetObjectId) {
34963
35290
  lines.push(` target ${event.targetObjectId}`);
34964
35291
  }
@@ -35001,12 +35328,102 @@ void main() {
35001
35328
  function formatEventPoseFields(pose) {
35002
35329
  return [
35003
35330
  ...formatPlacement(pose.placement),
35331
+ ...pose.trajectorySegmentId ? [`segment ${pose.trajectorySegmentId}`] : [],
35332
+ ...pose.trajectoryManeuverId ? [`maneuver ${pose.trajectoryManeuverId}`] : [],
35004
35333
  ...pose.epoch ? [`epoch ${quoteIfNeeded(pose.epoch)}`] : [],
35005
35334
  ...pose.referencePlane ? [`referencePlane ${quoteIfNeeded(pose.referencePlane)}`] : [],
35006
35335
  ...formatOptionalUnit("inner", pose.inner),
35007
35336
  ...formatOptionalUnit("outer", pose.outer)
35008
35337
  ];
35009
35338
  }
35339
+ function formatAtlasTrajectory(trajectory) {
35340
+ const lines = [`trajectory ${trajectory.id}`];
35341
+ if (trajectory.label) {
35342
+ lines.push(` label ${quoteIfNeeded(trajectory.label)}`);
35343
+ }
35344
+ if (trajectory.summary) {
35345
+ lines.push(` summary ${quoteIfNeeded(trajectory.summary)}`);
35346
+ }
35347
+ if (trajectory.craftObjectId) {
35348
+ lines.push(` craft ${trajectory.craftObjectId}`);
35349
+ }
35350
+ if (trajectory.tags.length > 0) {
35351
+ lines.push(` tags ${trajectory.tags.map(quoteIfNeeded).join(" ")}`);
35352
+ }
35353
+ if (trajectory.color) {
35354
+ lines.push(` color ${quoteIfNeeded(trajectory.color)}`);
35355
+ }
35356
+ if (trajectory.renderMode) {
35357
+ lines.push(` renderMode ${trajectory.renderMode}`);
35358
+ }
35359
+ if (trajectory.stroke) {
35360
+ lines.push(` stroke ${quoteIfNeeded(trajectory.stroke)}`);
35361
+ }
35362
+ if (trajectory.strokeWidth !== null && trajectory.strokeWidth !== void 0) {
35363
+ lines.push(` strokeWidth ${trajectory.strokeWidth}`);
35364
+ }
35365
+ if (trajectory.marker) {
35366
+ lines.push(` marker ${quoteIfNeeded(trajectory.marker)}`);
35367
+ }
35368
+ if (trajectory.labelMode) {
35369
+ lines.push(` labelMode ${quoteIfNeeded(trajectory.labelMode)}`);
35370
+ }
35371
+ if (trajectory.showWaypoints !== null && trajectory.showWaypoints !== void 0) {
35372
+ lines.push(` showWaypoints ${trajectory.showWaypoints ? "true" : "false"}`);
35373
+ }
35374
+ if (trajectory.hidden) {
35375
+ lines.push(" hidden true");
35376
+ }
35377
+ for (const segment of [...trajectory.segments].sort(compareIdLike)) {
35378
+ lines.push("");
35379
+ lines.push(` segment ${segment.id}`);
35380
+ for (const field of formatTrajectorySegmentFields(segment)) {
35381
+ lines.push(` ${field}`);
35382
+ }
35383
+ for (const maneuver of [...segment.maneuvers].sort(compareIdLike)) {
35384
+ lines.push(` maneuver ${maneuver.id}`);
35385
+ for (const field of formatTrajectoryManeuverFields(maneuver)) {
35386
+ lines.push(` ${field}`);
35387
+ }
35388
+ }
35389
+ }
35390
+ return lines;
35391
+ }
35392
+ function formatTrajectorySegmentFields(segment) {
35393
+ return [
35394
+ `kind ${segment.kind}`,
35395
+ ...segment.label ? [`label ${quoteIfNeeded(segment.label)}`] : [],
35396
+ ...segment.summary ? [`summary ${quoteIfNeeded(segment.summary)}`] : [],
35397
+ ...segment.fromObjectId ? [`from ${segment.fromObjectId}`] : [],
35398
+ ...segment.toObjectId ? [`to ${segment.toObjectId}`] : [],
35399
+ ...segment.aroundObjectId ? [`around ${segment.aroundObjectId}`] : [],
35400
+ ...segment.assist?.objectId ? [`assist ${segment.assist.objectId}`] : [],
35401
+ ...segment.epoch ? [`epoch ${quoteIfNeeded(segment.epoch)}`] : [],
35402
+ ...formatOptionalUnit("periapsis", segment.periapsis),
35403
+ ...formatOptionalUnit("apoapsis", segment.apoapsis),
35404
+ ...formatOptionalUnit("inclination", segment.inclination),
35405
+ ...formatOptionalUnit("duration", segment.duration),
35406
+ ...formatOptionalUnit("deltaV", segment.deltaV),
35407
+ ...formatOptionalUnit("phaseAngle", segment.phaseAngle),
35408
+ ...formatOptionalUnit("turnAngle", segment.turnAngle),
35409
+ ...formatOptionalUnit("energy", segment.energy),
35410
+ ...segment.waypointLabel ? [`waypointLabel ${quoteIfNeeded(segment.waypointLabel)}`] : [],
35411
+ ...segment.waypointDate ? [`waypointDate ${quoteIfNeeded(segment.waypointDate)}`] : [],
35412
+ ...segment.renderHidden !== null && segment.renderHidden !== void 0 ? [`renderHidden ${segment.renderHidden ? "true" : "false"}`] : [],
35413
+ ...segment.sampleDensity !== null && segment.sampleDensity !== void 0 ? [`sampleDensity ${segment.sampleDensity}`] : [],
35414
+ ...segment.notes.length > 0 ? [`notes ${segment.notes.map(quoteIfNeeded).join(" ")}`] : []
35415
+ ];
35416
+ }
35417
+ function formatTrajectoryManeuverFields(maneuver) {
35418
+ return [
35419
+ `kind ${quoteIfNeeded(maneuver.kind)}`,
35420
+ ...maneuver.label ? [`label ${quoteIfNeeded(maneuver.label)}`] : [],
35421
+ ...maneuver.epoch ? [`epoch ${quoteIfNeeded(maneuver.epoch)}`] : [],
35422
+ ...formatOptionalUnit("deltaV", maneuver.deltaV),
35423
+ ...formatOptionalUnit("duration", maneuver.duration),
35424
+ ...maneuver.notes.length > 0 ? [`notes ${maneuver.notes.map(quoteIfNeeded).join(" ")}`] : []
35425
+ ];
35426
+ }
35010
35427
  function hasCameraValues(camera) {
35011
35428
  return camera.azimuth !== null || camera.elevation !== null || camera.roll !== null || camera.distance !== null;
35012
35429
  }
@@ -35051,7 +35468,7 @@ void main() {
35051
35468
  if (orbitFront !== void 0 || orbitBack !== void 0) {
35052
35469
  tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
35053
35470
  }
35054
- for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
35471
+ for (const key of ["background", "guides", "relations", "events", "objects", "trajectories", "labels", "metadata"]) {
35055
35472
  if (layers[key] !== void 0) {
35056
35473
  tokens.push(layers[key] ? key : `-${key}`);
35057
35474
  }
@@ -35098,10 +35515,12 @@ void main() {
35098
35515
  return 5;
35099
35516
  case "ring":
35100
35517
  return 6;
35101
- case "structure":
35518
+ case "craft":
35102
35519
  return 7;
35103
- case "phenomenon":
35520
+ case "structure":
35104
35521
  return 8;
35522
+ case "phenomenon":
35523
+ return 9;
35105
35524
  }
35106
35525
  }
35107
35526
  function quoteIfNeeded(value) {
@@ -35278,6 +35697,7 @@ void main() {
35278
35697
  const objectMap = new Map(document2.objects.map((object) => [object.id, object]));
35279
35698
  const groupIds = new Set(document2.groups.map((group) => group.id));
35280
35699
  const eventIds = new Set(document2.events.map((event) => event.id));
35700
+ const trajectoryMap = new Map(document2.trajectories.map((trajectory) => [trajectory.id, trajectory]));
35281
35701
  if (!document2.system) {
35282
35702
  diagnostics.push(error("validate.system.required", "Atlas documents must declare exactly one system."));
35283
35703
  }
@@ -35288,6 +35708,7 @@ void main() {
35288
35708
  ["annotation", document2.system?.annotations.map((annotation) => annotation.id) ?? []],
35289
35709
  ["relation", document2.relations.map((relation) => relation.id)],
35290
35710
  ["event", document2.events.map((event) => event.id)],
35711
+ ["trajectory", document2.trajectories.map((trajectory) => trajectory.id)],
35291
35712
  ["object", document2.objects.map((object) => object.id)]
35292
35713
  ]) {
35293
35714
  for (const id of ids) {
@@ -35306,10 +35727,13 @@ void main() {
35306
35727
  validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap);
35307
35728
  }
35308
35729
  for (const object of document2.objects) {
35309
- validateObject(object, document2.system, objectMap, groupIds, diagnostics);
35730
+ validateObject(object, document2.system, objectMap, groupIds, trajectoryMap, diagnostics);
35310
35731
  }
35311
35732
  for (const event of document2.events) {
35312
- validateEvent(event, document2.system, objectMap, diagnostics);
35733
+ validateEvent(event, document2.system, objectMap, trajectoryMap, diagnostics);
35734
+ }
35735
+ for (const trajectory of document2.trajectories) {
35736
+ validateTrajectory(trajectory, objectMap, diagnostics);
35313
35737
  }
35314
35738
  return diagnostics;
35315
35739
  }
@@ -35347,7 +35771,7 @@ void main() {
35347
35771
  validateProjection(viewpoint.projection, diagnostics, `viewpoint.${viewpoint.id}.projection`, viewpoint.id);
35348
35772
  validateCamera(viewpoint.camera, viewpoint.projection, viewpoint.rotationDeg, diagnostics, viewpoint.id, viewpoint.focusObjectId, viewpoint.selectedObjectId, filter, objectMap);
35349
35773
  }
35350
- function validateObject(object, system, objectMap, groupIds, diagnostics) {
35774
+ function validateObject(object, system, objectMap, groupIds, trajectoryMap, diagnostics) {
35351
35775
  const placement = object.placement;
35352
35776
  const orbitPlacement = placement?.mode === "orbit" ? placement : null;
35353
35777
  const parentObject = placement?.mode === "orbit" ? objectMap.get(placement.target) ?? null : null;
@@ -35364,6 +35788,13 @@ void main() {
35364
35788
  if (typeof object.referencePlane === "string" && !object.referencePlane.trim()) {
35365
35789
  diagnostics.push(warn("validate.referencePlane.empty", `Object "${object.id}" defines an empty reference plane string.`, object.id, "referencePlane"));
35366
35790
  }
35791
+ if (object.trajectoryId) {
35792
+ if (!trajectoryMap.has(object.trajectoryId)) {
35793
+ diagnostics.push(error("validate.trajectory.object.unknown", `Unknown trajectory "${object.trajectoryId}" on "${object.id}".`, object.id, "trajectory"));
35794
+ } else if (!isTrajectoryCapableObject(object)) {
35795
+ 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"));
35796
+ }
35797
+ }
35367
35798
  if (orbitPlacement) {
35368
35799
  if (!objectMap.has(orbitPlacement.target)) {
35369
35800
  diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
@@ -35390,8 +35821,8 @@ void main() {
35390
35821
  }
35391
35822
  }
35392
35823
  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"));
35824
+ if (object.type !== "craft" && object.type !== "structure" && object.type !== "phenomenon") {
35825
+ 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
35826
  }
35396
35827
  if (!validateAtTarget(object, objectMap, diagnostics)) {
35397
35828
  diagnostics.push(error("validate.at.target.unknown", `Unknown at-reference target "${placement.target}" on "${object.id}".`, object.id, "at"));
@@ -35435,7 +35866,7 @@ void main() {
35435
35866
  }
35436
35867
  }
35437
35868
  }
35438
- function validateEvent(event, system, objectMap, diagnostics) {
35869
+ function validateEvent(event, system, objectMap, trajectoryMap, diagnostics) {
35439
35870
  const fieldPrefix = `event.${event.id}`;
35440
35871
  const referencedIds = /* @__PURE__ */ new Set();
35441
35872
  if (!event.kind.trim()) {
@@ -35447,6 +35878,9 @@ void main() {
35447
35878
  if (typeof event.referencePlane === "string" && !event.referencePlane.trim()) {
35448
35879
  diagnostics.push(warn("validate.event.referencePlane.empty", `Event "${event.id}" defines an empty reference plane string.`, void 0, `${fieldPrefix}.referencePlane`));
35449
35880
  }
35881
+ if (event.trajectoryId && !trajectoryMap.has(event.trajectoryId)) {
35882
+ diagnostics.push(error("validate.event.trajectory.unknown", `Unknown trajectory "${event.trajectoryId}" on event "${event.id}".`, void 0, `${fieldPrefix}.trajectory`));
35883
+ }
35450
35884
  if (!event.targetObjectId && event.participantObjectIds.length === 0) {
35451
35885
  diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
35452
35886
  }
@@ -35493,19 +35927,25 @@ void main() {
35493
35927
  if (!referencedIds.has(pose.objectId)) {
35494
35928
  diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
35495
35929
  }
35496
- validateEventPose(pose, object, event, system, objectMap, diagnostics, poseFieldPrefix, event.id);
35930
+ validateEventPose(pose, object, event, system, objectMap, trajectoryMap, diagnostics, poseFieldPrefix, event.id);
35497
35931
  }
35498
35932
  const missingPoseIds = [...referencedIds].filter((objectId) => !poseIds.has(objectId));
35499
35933
  if (event.positions.length > 0 && missingPoseIds.length > 0) {
35500
35934
  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
35935
  }
35502
35936
  }
35503
- function validateEventPose(pose, object, event, system, objectMap, diagnostics, fieldPrefix, eventId) {
35937
+ function validateEventPose(pose, object, event, system, objectMap, trajectoryMap, diagnostics, fieldPrefix, eventId) {
35504
35938
  const placement = pose.placement;
35505
35939
  if (!placement) {
35506
35940
  diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
35507
35941
  return;
35508
35942
  }
35943
+ if (pose.trajectorySegmentId && !findTrajectorySegment(trajectoryMap, pose.trajectorySegmentId)) {
35944
+ diagnostics.push(error("validate.event.pose.segment.unknown", `Unknown trajectory segment "${pose.trajectorySegmentId}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.segment`));
35945
+ }
35946
+ if (pose.trajectoryManeuverId && !findTrajectoryManeuver(trajectoryMap, pose.trajectoryManeuverId)) {
35947
+ diagnostics.push(error("validate.event.pose.maneuver.unknown", `Unknown trajectory maneuver "${pose.trajectoryManeuverId}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.maneuver`));
35948
+ }
35509
35949
  if (placement.mode === "orbit") {
35510
35950
  if (!objectMap.has(placement.target)) {
35511
35951
  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 +35974,8 @@ void main() {
35534
35974
  return;
35535
35975
  }
35536
35976
  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`));
35977
+ if (object.type !== "craft" && object.type !== "structure" && object.type !== "phenomenon") {
35978
+ 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
35979
  }
35540
35980
  const reference = placement.reference;
35541
35981
  if (reference.kind === "named" && !objectMap.has(reference.name)) {
@@ -35551,6 +35991,78 @@ void main() {
35551
35991
  }
35552
35992
  }
35553
35993
  }
35994
+ function validateTrajectory(trajectory, objectMap, diagnostics) {
35995
+ if (trajectory.craftObjectId) {
35996
+ const craft = objectMap.get(trajectory.craftObjectId);
35997
+ if (!craft) {
35998
+ diagnostics.push(error("validate.trajectory.craft.unknown", `Unknown craft "${trajectory.craftObjectId}" on trajectory "${trajectory.id}".`, void 0, `trajectory.${trajectory.id}.craft`));
35999
+ } else if (!isTrajectoryCapableObject(craft)) {
36000
+ diagnostics.push(error("validate.trajectory.craft.invalidType", `Trajectory "${trajectory.id}" targets "${trajectory.craftObjectId}", which is not craft-like.`, void 0, `trajectory.${trajectory.id}.craft`));
36001
+ }
36002
+ }
36003
+ for (const segment of trajectory.segments) {
36004
+ validateTrajectorySegment(trajectory.id, segment, objectMap, diagnostics);
36005
+ }
36006
+ }
36007
+ function validateTrajectorySegment(trajectoryId, segment, objectMap, diagnostics) {
36008
+ const fieldPrefix = `trajectory.${trajectoryId}.segment.${segment.id}`;
36009
+ for (const [field, objectId] of [
36010
+ ["from", segment.fromObjectId],
36011
+ ["to", segment.toObjectId],
36012
+ ["around", segment.aroundObjectId]
36013
+ ]) {
36014
+ if (objectId && !objectMap.has(objectId)) {
36015
+ diagnostics.push(error(`validate.trajectory.segment.${field}.unknown`, `Unknown ${field} object "${objectId}" on trajectory "${trajectoryId}" segment "${segment.id}".`, void 0, `${fieldPrefix}.${field}`));
36016
+ }
36017
+ }
36018
+ if (segment.assist?.objectId && !objectMap.has(segment.assist.objectId)) {
36019
+ diagnostics.push(error("validate.trajectory.segment.assist.unknown", `Unknown assist object "${segment.assist.objectId}" on trajectory "${trajectoryId}" segment "${segment.id}".`, void 0, `${fieldPrefix}.assist`));
36020
+ }
36021
+ if (segment.kind === "flyby" && !segment.assist?.objectId) {
36022
+ 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`));
36023
+ }
36024
+ if ((segment.kind === "capture" || segment.kind === "departure") && !segment.toObjectId && !segment.aroundObjectId) {
36025
+ diagnostics.push(error("validate.trajectory.segment.target.required", `Trajectory "${trajectoryId}" segment "${segment.id}" requires a target reference.`, void 0, `${fieldPrefix}.to`));
36026
+ }
36027
+ for (const maneuver of segment.maneuvers) {
36028
+ validateTrajectoryManeuver(trajectoryId, segment.id, maneuver, diagnostics);
36029
+ }
36030
+ }
36031
+ function validateTrajectoryManeuver(trajectoryId, segmentId, maneuver, diagnostics) {
36032
+ if (!maneuver.kind.trim()) {
36033
+ 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`));
36034
+ }
36035
+ }
36036
+ function isTrajectoryCapableObject(object) {
36037
+ if (object.type === "craft") {
36038
+ return true;
36039
+ }
36040
+ if (object.type !== "structure") {
36041
+ return false;
36042
+ }
36043
+ const kind = typeof object.properties.kind === "string" ? object.properties.kind.toLowerCase() : "";
36044
+ return kind === "ship" || kind === "probe" || kind === "station";
36045
+ }
36046
+ function findTrajectorySegment(trajectories, segmentId) {
36047
+ for (const trajectory of trajectories.values()) {
36048
+ const match = trajectory.segments.find((segment) => segment.id === segmentId);
36049
+ if (match) {
36050
+ return match;
36051
+ }
36052
+ }
36053
+ return null;
36054
+ }
36055
+ function findTrajectoryManeuver(trajectories, maneuverId) {
36056
+ for (const trajectory of trajectories.values()) {
36057
+ for (const segment of trajectory.segments) {
36058
+ const match = segment.maneuvers.find((maneuver) => maneuver.id === maneuverId);
36059
+ if (match) {
36060
+ return match;
36061
+ }
36062
+ }
36063
+ }
36064
+ return null;
36065
+ }
35554
36066
  function validateAtTarget(object, objectMap, diagnostics) {
35555
36067
  const reference = object.placement?.mode === "at" ? object.placement.reference : null;
35556
36068
  if (!reference) {
@@ -35731,6 +36243,14 @@ void main() {
35731
36243
  "habitability",
35732
36244
  "settlement"
35733
36245
  ]);
36246
+ var TRAJECTORY_SEGMENT_KINDS = /* @__PURE__ */ new Set([
36247
+ "departure",
36248
+ "transfer",
36249
+ "flyby",
36250
+ "capture",
36251
+ "stationkeeping",
36252
+ "escape"
36253
+ ]);
35734
36254
  var DRAFT_OBJECT_FIELD_SPECS = /* @__PURE__ */ new Map();
35735
36255
  for (const key of [
35736
36256
  "orbit",
@@ -35762,7 +36282,8 @@ void main() {
35762
36282
  "outer",
35763
36283
  "on",
35764
36284
  "source",
35765
- "cycle"
36285
+ "cycle",
36286
+ "trajectory"
35766
36287
  ]) {
35767
36288
  const schema = getFieldSchema(key);
35768
36289
  if (schema) {
@@ -35787,11 +36308,12 @@ void main() {
35787
36308
  { key: "derive", inlineMode: "pair", allowRepeat: true },
35788
36309
  { key: "validate", inlineMode: "single", allowRepeat: true },
35789
36310
  { key: "locked", inlineMode: "multiple", allowRepeat: false },
35790
- { key: "tolerance", inlineMode: "pair", allowRepeat: true }
36311
+ { key: "tolerance", inlineMode: "pair", allowRepeat: true },
36312
+ { key: "trajectory", inlineMode: "single", allowRepeat: false }
35791
36313
  ]) {
35792
36314
  DRAFT_OBJECT_FIELD_SPECS.set(spec.key, {
35793
36315
  key: spec.key,
35794
- version: "2.1",
36316
+ version: spec.key === "trajectory" ? "3.0" : "2.1",
35795
36317
  inlineMode: spec.inlineMode,
35796
36318
  allowRepeat: spec.allowRepeat
35797
36319
  });
@@ -35812,7 +36334,9 @@ void main() {
35812
36334
  "inner",
35813
36335
  "outer",
35814
36336
  "epoch",
35815
- "referencePlane"
36337
+ "referencePlane",
36338
+ "segment",
36339
+ "maneuver"
35816
36340
  ]);
35817
36341
  function parseWorldOrbitAtlas(source) {
35818
36342
  return parseAtlasSource(source);
@@ -35832,6 +36356,7 @@ void main() {
35832
36356
  const groups = [];
35833
36357
  const relations = [];
35834
36358
  const events = [];
36359
+ const trajectories = [];
35835
36360
  const eventPoseNodes = /* @__PURE__ */ new Map();
35836
36361
  let sawDefaults = false;
35837
36362
  let sawAtlas = false;
@@ -35840,6 +36365,7 @@ void main() {
35840
36365
  const groupIds = /* @__PURE__ */ new Set();
35841
36366
  const relationIds = /* @__PURE__ */ new Set();
35842
36367
  const eventIds = /* @__PURE__ */ new Set();
36368
+ const trajectoryIds = /* @__PURE__ */ new Set();
35843
36369
  for (let index = 0; index < lines.length; index++) {
35844
36370
  const rawLine = lines[index];
35845
36371
  const lineNumber = index + 1;
@@ -35870,7 +36396,7 @@ void main() {
35870
36396
  continue;
35871
36397
  }
35872
36398
  if (indent === 0) {
35873
- section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, { sawDefaults, sawAtlas });
36399
+ section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, trajectories, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, trajectoryIds, { sawDefaults, sawAtlas });
35874
36400
  if (section.kind === "system") {
35875
36401
  system = section.system;
35876
36402
  } else if (section.kind === "defaults") {
@@ -35886,7 +36412,7 @@ void main() {
35886
36412
  handleSectionLine(section, indent, tokens, lineNumber);
35887
36413
  }
35888
36414
  if (!sawSchemaHeader) {
35889
- throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
36415
+ throw new WorldOrbitError('Missing required atlas schema header "schema 2.0", "schema 3.0", or "schema 3.1"');
35890
36416
  }
35891
36417
  const objects = objectNodes.map((node) => normalizeDraftObject(node, sourceSchemaVersion, diagnostics));
35892
36418
  const normalizedEvents = events.map((event) => normalizeDraftEvent(event, eventPoseNodes.get(event.id) ?? []));
@@ -35899,6 +36425,7 @@ void main() {
35899
36425
  groups,
35900
36426
  relations,
35901
36427
  events: normalizedEvents,
36428
+ trajectories,
35902
36429
  objects,
35903
36430
  diagnostics
35904
36431
  };
@@ -35928,13 +36455,13 @@ void main() {
35928
36455
  return document2;
35929
36456
  }
35930
36457
  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);
36458
+ if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1", "2.5", "2.6", "3.0", "3.1"].includes(tokens[1].value.toLowerCase())) {
36459
+ throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", "schema 2.5", "schema 2.6", "schema 3.0", "schema 3.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
35933
36460
  }
35934
36461
  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";
36462
+ return version === "2.6" ? "2.6" : version === "3.0" ? "3.0" : version === "3.1" ? "3.1" : version === "2.5" ? "2.5" : version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
35936
36463
  }
35937
- function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
36464
+ function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, trajectories, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, trajectoryIds, flags) {
35938
36465
  const keyword = tokens[0]?.value.toLowerCase();
35939
36466
  switch (keyword) {
35940
36467
  case "system":
@@ -35988,6 +36515,9 @@ void main() {
35988
36515
  case "event":
35989
36516
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "event", { line, column: tokens[0].column });
35990
36517
  return startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics);
36518
+ case "trajectory":
36519
+ warnIfSchema30Feature(sourceSchemaVersion, diagnostics, "trajectory", { line, column: tokens[0].column });
36520
+ return startTrajectorySection(tokens, line, trajectories, trajectoryIds, sourceSchemaVersion, diagnostics);
35991
36521
  case "object":
35992
36522
  return startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes);
35993
36523
  default:
@@ -36195,6 +36725,51 @@ void main() {
36195
36725
  activePoseSeenFields: /* @__PURE__ */ new Set()
36196
36726
  };
36197
36727
  }
36728
+ function startTrajectorySection(tokens, line, trajectories, trajectoryIds, sourceSchemaVersion, diagnostics) {
36729
+ if (tokens.length !== 2) {
36730
+ throw new WorldOrbitError("Invalid trajectory declaration", line, tokens[0]?.column ?? 1);
36731
+ }
36732
+ const id = normalizeIdentifier2(tokens[1].value);
36733
+ if (!id) {
36734
+ throw new WorldOrbitError("Trajectory id must not be empty", line, tokens[1].column);
36735
+ }
36736
+ if (trajectoryIds.has(id)) {
36737
+ throw new WorldOrbitError(`Duplicate trajectory id "${id}"`, line, tokens[1].column);
36738
+ }
36739
+ const trajectory = {
36740
+ id,
36741
+ label: humanizeIdentifier3(id),
36742
+ summary: null,
36743
+ craftObjectId: null,
36744
+ tags: [],
36745
+ color: null,
36746
+ renderMode: null,
36747
+ stroke: null,
36748
+ strokeWidth: null,
36749
+ marker: null,
36750
+ labelMode: null,
36751
+ showWaypoints: null,
36752
+ hidden: false,
36753
+ segments: []
36754
+ };
36755
+ trajectories.push(trajectory);
36756
+ trajectoryIds.add(id);
36757
+ return {
36758
+ kind: "trajectory",
36759
+ trajectory,
36760
+ sourceSchemaVersion,
36761
+ diagnostics,
36762
+ seenFields: /* @__PURE__ */ new Set(),
36763
+ inSegment: false,
36764
+ segmentIndent: null,
36765
+ activeSegment: null,
36766
+ activeSegmentSeenFields: /* @__PURE__ */ new Set(),
36767
+ inManeuver: false,
36768
+ maneuverIndent: null,
36769
+ activeManeuver: null,
36770
+ activeManeuverSeenFields: /* @__PURE__ */ new Set()
36771
+ };
36772
+ }
36198
36773
  function startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes) {
36199
36774
  if (tokens.length < 3) {
36200
36775
  throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
@@ -36254,6 +36829,9 @@ void main() {
36254
36829
  case "event":
36255
36830
  applyEventField(section, indent, tokens, line);
36256
36831
  return;
36832
+ case "trajectory":
36833
+ applyTrajectoryField(section, indent, tokens, line);
36834
+ return;
36257
36835
  case "object":
36258
36836
  applyObjectField(section, indent, tokens, line);
36259
36837
  return;
@@ -36572,6 +37150,12 @@ void main() {
36572
37150
  column: tokens[0]?.column ?? 1
36573
37151
  });
36574
37152
  }
37153
+ if (tokens[0]?.value === "segment" || tokens[0]?.value === "maneuver") {
37154
+ warnIfSchema30Feature(section.sourceSchemaVersion, section.diagnostics, `pose.${tokens[0].value}`, {
37155
+ line,
37156
+ column: tokens[0]?.column ?? 1
37157
+ });
37158
+ }
36575
37159
  section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
36576
37160
  return;
36577
37161
  }
@@ -36614,6 +37198,13 @@ void main() {
36614
37198
  case "summary":
36615
37199
  section.event.summary = joinFieldValue(tokens, line);
36616
37200
  return;
37201
+ case "trajectory":
37202
+ warnIfSchema30Feature(section.sourceSchemaVersion, section.diagnostics, "event.trajectory", {
37203
+ line,
37204
+ column: tokens[0].column
37205
+ });
37206
+ section.event.trajectoryId = joinFieldValue(tokens, line);
37207
+ return;
36617
37208
  case "target":
36618
37209
  section.event.targetObjectId = joinFieldValue(tokens, line);
36619
37210
  return;
@@ -36656,6 +37247,302 @@ void main() {
36656
37247
  throw new WorldOrbitError(`Unknown event field "${tokens[0].value}"`, line, tokens[0].column);
36657
37248
  }
36658
37249
  }
37250
+ function applyTrajectoryField(section, indent, tokens, line) {
37251
+ if (section.activeManeuver && indent <= (section.maneuverIndent ?? 0)) {
37252
+ section.activeManeuver = null;
37253
+ section.maneuverIndent = null;
37254
+ section.activeManeuverSeenFields.clear();
37255
+ section.inManeuver = false;
37256
+ }
37257
+ if (section.activeSegment && indent <= (section.segmentIndent ?? 0)) {
37258
+ section.activeSegment = null;
37259
+ section.segmentIndent = null;
37260
+ section.activeSegmentSeenFields.clear();
37261
+ section.inSegment = false;
37262
+ }
37263
+ if (section.activeManeuver) {
37264
+ applyTrajectoryManeuverField(section, tokens, line);
37265
+ return;
37266
+ }
37267
+ if (section.activeSegment) {
37268
+ if (tokens[0]?.value.toLowerCase() === "maneuver") {
37269
+ if (tokens.length !== 2) {
37270
+ throw new WorldOrbitError("Invalid trajectory maneuver declaration", line, tokens[0]?.column ?? 1);
37271
+ }
37272
+ const id = normalizeIdentifier2(tokens[1].value);
37273
+ if (!id) {
37274
+ throw new WorldOrbitError("Trajectory maneuver id must not be empty", line, tokens[1].column);
37275
+ }
37276
+ if (section.activeSegment.maneuvers.some((maneuver2) => maneuver2.id === id)) {
37277
+ throw new WorldOrbitError(`Duplicate trajectory maneuver id "${id}"`, line, tokens[1].column);
37278
+ }
37279
+ const maneuver = {
37280
+ id,
37281
+ kind: "burn",
37282
+ label: null,
37283
+ epoch: null,
37284
+ notes: []
37285
+ };
37286
+ section.activeSegment.maneuvers.push(maneuver);
37287
+ section.activeManeuver = maneuver;
37288
+ section.inManeuver = true;
37289
+ section.maneuverIndent = indent;
37290
+ section.activeManeuverSeenFields = /* @__PURE__ */ new Set();
37291
+ return;
37292
+ }
37293
+ applyTrajectorySegmentField(section, tokens, line);
37294
+ return;
37295
+ }
37296
+ if (tokens[0]?.value.toLowerCase() === "segment") {
37297
+ if (tokens.length !== 2) {
37298
+ throw new WorldOrbitError("Invalid trajectory segment declaration", line, tokens[0]?.column ?? 1);
37299
+ }
37300
+ const id = normalizeIdentifier2(tokens[1].value);
37301
+ if (!id) {
37302
+ throw new WorldOrbitError("Trajectory segment id must not be empty", line, tokens[1].column);
37303
+ }
37304
+ if (section.trajectory.segments.some((segment2) => segment2.id === id)) {
37305
+ throw new WorldOrbitError(`Duplicate trajectory segment id "${id}"`, line, tokens[1].column);
37306
+ }
37307
+ const segment = {
37308
+ id,
37309
+ kind: "transfer",
37310
+ label: null,
37311
+ summary: null,
37312
+ fromObjectId: null,
37313
+ toObjectId: null,
37314
+ aroundObjectId: null,
37315
+ assist: null,
37316
+ epoch: null,
37317
+ waypointLabel: null,
37318
+ waypointDate: null,
37319
+ renderHidden: null,
37320
+ sampleDensity: null,
37321
+ notes: [],
37322
+ maneuvers: []
37323
+ };
37324
+ section.trajectory.segments.push(segment);
37325
+ section.activeSegment = {
37326
+ id,
37327
+ fields: [],
37328
+ maneuvers: segment.maneuvers,
37329
+ assist: null,
37330
+ location: { line, column: tokens[0].column }
37331
+ };
37332
+ section.inSegment = true;
37333
+ section.segmentIndent = indent;
37334
+ section.activeSegmentSeenFields = /* @__PURE__ */ new Set();
37335
+ return;
37336
+ }
37337
+ const key = requireUniqueField(tokens, section.seenFields, line);
37338
+ const value = joinFieldValue(tokens, line);
37339
+ switch (key) {
37340
+ case "label":
37341
+ section.trajectory.label = value;
37342
+ return;
37343
+ case "summary":
37344
+ section.trajectory.summary = value;
37345
+ return;
37346
+ case "craft":
37347
+ section.trajectory.craftObjectId = value;
37348
+ return;
37349
+ case "tags":
37350
+ section.trajectory.tags = parseTokenList(tokens.slice(1), line, "tags");
37351
+ return;
37352
+ case "color":
37353
+ section.trajectory.color = value;
37354
+ return;
37355
+ case "rendermode":
37356
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "trajectory.renderMode", {
37357
+ line,
37358
+ column: tokens[0].column
37359
+ });
37360
+ if (value !== "illustrative" && value !== "solver" && value !== "auto") {
37361
+ throw new WorldOrbitError(`Unknown trajectory render mode "${value}"`, line, tokens[0].column);
37362
+ }
37363
+ section.trajectory.renderMode = value;
37364
+ return;
37365
+ case "stroke":
37366
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "trajectory.stroke", {
37367
+ line,
37368
+ column: tokens[0].column
37369
+ });
37370
+ section.trajectory.stroke = value;
37371
+ return;
37372
+ case "strokewidth":
37373
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "trajectory.strokeWidth", {
37374
+ line,
37375
+ column: tokens[0].column
37376
+ });
37377
+ section.trajectory.strokeWidth = parsePositiveNumber2(value, line, tokens[0].column, "strokeWidth");
37378
+ return;
37379
+ case "marker":
37380
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "trajectory.marker", {
37381
+ line,
37382
+ column: tokens[0].column
37383
+ });
37384
+ section.trajectory.marker = value;
37385
+ return;
37386
+ case "labelmode":
37387
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "trajectory.labelMode", {
37388
+ line,
37389
+ column: tokens[0].column
37390
+ });
37391
+ section.trajectory.labelMode = value;
37392
+ return;
37393
+ case "showwaypoints":
37394
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "trajectory.showWaypoints", {
37395
+ line,
37396
+ column: tokens[0].column
37397
+ });
37398
+ section.trajectory.showWaypoints = parseAtlasBoolean(value, "showWaypoints", {
37399
+ line,
37400
+ column: tokens[0].column
37401
+ });
37402
+ return;
37403
+ case "hidden":
37404
+ section.trajectory.hidden = parseAtlasBoolean(value, "hidden", {
37405
+ line,
37406
+ column: tokens[0].column
37407
+ });
37408
+ return;
37409
+ default:
37410
+ throw new WorldOrbitError(`Unknown trajectory field "${tokens[0].value}"`, line, tokens[0].column);
37411
+ }
37412
+ }
37413
+ function applyTrajectorySegmentField(section, tokens, line) {
37414
+ const segment = section.activeSegment;
37415
+ if (!segment) {
37416
+ return;
37417
+ }
37418
+ const key = requireUniqueField(tokens, section.activeSegmentSeenFields, line);
37419
+ const value = joinFieldValue(tokens, line);
37420
+ const target = section.trajectory.segments.find((entry) => entry.id === segment.id);
37421
+ switch (key) {
37422
+ case "kind": {
37423
+ const normalized = value.toLowerCase();
37424
+ if (!TRAJECTORY_SEGMENT_KINDS.has(normalized)) {
37425
+ throw new WorldOrbitError(`Unknown trajectory segment kind "${value}"`, line, tokens[0].column);
37426
+ }
37427
+ target.kind = normalized;
37428
+ return;
37429
+ }
37430
+ case "label":
37431
+ target.label = value;
37432
+ return;
37433
+ case "summary":
37434
+ target.summary = value;
37435
+ return;
37436
+ case "from":
37437
+ target.fromObjectId = value;
37438
+ return;
37439
+ case "to":
37440
+ target.toObjectId = value;
37441
+ return;
37442
+ case "around":
37443
+ target.aroundObjectId = value;
37444
+ return;
37445
+ case "assist":
37446
+ target.assist = {
37447
+ objectId: value,
37448
+ notes: []
37449
+ };
37450
+ return;
37451
+ case "epoch":
37452
+ target.epoch = value;
37453
+ return;
37454
+ case "periapsis":
37455
+ target.periapsis = parseAtlasUnitValue(value, { line, column: tokens[0].column }, "periapsis");
37456
+ return;
37457
+ case "apoapsis":
37458
+ target.apoapsis = parseAtlasUnitValue(value, { line, column: tokens[0].column }, "apoapsis");
37459
+ return;
37460
+ case "inclination":
37461
+ target.inclination = parseAtlasUnitValue(value, { line, column: tokens[0].column }, "inclination");
37462
+ return;
37463
+ case "duration":
37464
+ target.duration = parseAtlasUnitValue(value, { line, column: tokens[0].column }, "duration");
37465
+ return;
37466
+ case "deltav":
37467
+ target.deltaV = parseAtlasUnitValue(value, { line, column: tokens[0].column });
37468
+ return;
37469
+ case "phaseangle":
37470
+ target.phaseAngle = parseAtlasUnitValue(value, { line, column: tokens[0].column }, "phaseAngle");
37471
+ return;
37472
+ case "turnangle":
37473
+ target.turnAngle = parseAtlasUnitValue(value, { line, column: tokens[0].column }, "turnAngle");
37474
+ return;
37475
+ case "energy":
37476
+ target.energy = parseAtlasUnitValue(value, { line, column: tokens[0].column });
37477
+ return;
37478
+ case "waypointlabel":
37479
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "segment.waypointLabel", {
37480
+ line,
37481
+ column: tokens[0].column
37482
+ });
37483
+ target.waypointLabel = value;
37484
+ return;
37485
+ case "waypointdate":
37486
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "segment.waypointDate", {
37487
+ line,
37488
+ column: tokens[0].column
37489
+ });
37490
+ target.waypointDate = value;
37491
+ return;
37492
+ case "renderhidden":
37493
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "segment.renderHidden", {
37494
+ line,
37495
+ column: tokens[0].column
37496
+ });
37497
+ target.renderHidden = parseAtlasBoolean(value, "renderHidden", {
37498
+ line,
37499
+ column: tokens[0].column
37500
+ });
37501
+ return;
37502
+ case "sampledensity":
37503
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "segment.sampleDensity", {
37504
+ line,
37505
+ column: tokens[0].column
37506
+ });
37507
+ target.sampleDensity = parsePositiveNumber2(value, line, tokens[0].column, "sampleDensity");
37508
+ return;
37509
+ case "notes":
37510
+ target.notes = parseTokenList(tokens.slice(1), line, "notes");
37511
+ return;
37512
+ default:
37513
+ throw new WorldOrbitError(`Unknown trajectory segment field "${tokens[0].value}"`, line, tokens[0].column);
37514
+ }
37515
+ }
37516
+ function applyTrajectoryManeuverField(section, tokens, line) {
37517
+ const maneuver = section.activeManeuver;
37518
+ if (!maneuver) {
37519
+ return;
37520
+ }
37521
+ const key = requireUniqueField(tokens, section.activeManeuverSeenFields, line);
37522
+ const value = joinFieldValue(tokens, line);
37523
+ switch (key) {
37524
+ case "kind":
37525
+ maneuver.kind = value;
37526
+ return;
37527
+ case "label":
37528
+ maneuver.label = value;
37529
+ return;
37530
+ case "epoch":
37531
+ maneuver.epoch = value;
37532
+ return;
37533
+ case "deltav":
37534
+ maneuver.deltaV = parseAtlasUnitValue(value, { line, column: tokens[0].column });
37535
+ return;
37536
+ case "duration":
37537
+ maneuver.duration = parseAtlasUnitValue(value, { line, column: tokens[0].column }, "duration");
37538
+ return;
37539
+ case "notes":
37540
+ maneuver.notes = parseTokenList(tokens.slice(1), line, "notes");
37541
+ return;
37542
+ default:
37543
+ throw new WorldOrbitError(`Unknown trajectory maneuver field "${tokens[0].value}"`, line, tokens[0].column);
37544
+ }
37545
+ }
36659
37546
  function parseEventPoseField(tokens, line, seenFields) {
36660
37547
  if (tokens.length < 2) {
36661
37548
  throw new WorldOrbitError("Invalid event pose field line", line, tokens[0]?.column ?? 1);
@@ -36743,7 +37630,7 @@ void main() {
36743
37630
  layers["orbits-front"] = enabled;
36744
37631
  continue;
36745
37632
  }
36746
- if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "events" || raw === "objects" || raw === "labels" || raw === "metadata") {
37633
+ if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "events" || raw === "objects" || raw === "labels" || raw === "metadata" || raw === "trajectories") {
36747
37634
  if (raw === "events" && sourceSchemaVersion && diagnostics) {
36748
37635
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "layers.events", {
36749
37636
  line,
@@ -36861,6 +37748,16 @@ void main() {
36861
37748
  line,
36862
37749
  column: keyToken.column
36863
37750
  });
37751
+ } else if (spec.version === "3.0") {
37752
+ warnIfSchema30Feature(sourceSchemaVersion, diagnostics, keyToken.value, {
37753
+ line,
37754
+ column: keyToken.column
37755
+ });
37756
+ } else if (spec.version === "3.1") {
37757
+ warnIfSchema31Feature(sourceSchemaVersion, diagnostics, keyToken.value, {
37758
+ line,
37759
+ column: keyToken.column
37760
+ });
36864
37761
  }
36865
37762
  index++;
36866
37763
  const valueTokens = [];
@@ -36911,6 +37808,16 @@ void main() {
36911
37808
  line,
36912
37809
  column: tokens[0].column
36913
37810
  });
37811
+ } else if (spec.version === "3.0") {
37812
+ warnIfSchema30Feature(sourceSchemaVersion, diagnostics, tokens[0].value, {
37813
+ line,
37814
+ column: tokens[0].column
37815
+ });
37816
+ } else if (spec.version === "3.1") {
37817
+ warnIfSchema31Feature(sourceSchemaVersion, diagnostics, tokens[0].value, {
37818
+ line,
37819
+ column: tokens[0].column
37820
+ });
36914
37821
  }
36915
37822
  const field = {
36916
37823
  type: "field",
@@ -36950,6 +37857,7 @@ void main() {
36950
37857
  const tolerances = fieldMap.get("tolerance")?.map((field) => parseToleranceField(field));
36951
37858
  const typedBlocks = normalizeTypedBlocks(node.typedBlockEntries);
36952
37859
  const info2 = normalizeInfoEntries(node.infoEntries, "info");
37860
+ const trajectoryId = parseOptionalJoinedValue(fieldMap.get("trajectory")?.[0]);
36953
37861
  const object = {
36954
37862
  type: node.objectType,
36955
37863
  id: node.id,
@@ -36979,11 +37887,16 @@ void main() {
36979
37887
  object.tolerances = tolerances;
36980
37888
  if (typedBlocks && Object.keys(typedBlocks).length > 0)
36981
37889
  object.typedBlocks = typedBlocks;
37890
+ if (trajectoryId)
37891
+ object.trajectoryId = trajectoryId;
36982
37892
  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) {
37893
+ 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
37894
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
36985
37895
  }
36986
37896
  }
37897
+ if (object.trajectoryId) {
37898
+ warnIfSchema30Feature(sourceSchemaVersion, diagnostics, `${node.id}.trajectory`, node.location);
37899
+ }
36987
37900
  return object;
36988
37901
  }
36989
37902
  function normalizeDraftEvent(event, rawPoses) {
@@ -37000,6 +37913,8 @@ void main() {
37000
37913
  return {
37001
37914
  objectId: rawPose.objectId,
37002
37915
  placement,
37916
+ trajectorySegmentId: parseOptionalJoinedValue(fieldMap.get("segment")?.[0]),
37917
+ trajectoryManeuverId: parseOptionalJoinedValue(fieldMap.get("maneuver")?.[0]),
37003
37918
  inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
37004
37919
  outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer"),
37005
37920
  epoch: parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]),
@@ -37215,6 +38130,32 @@ void main() {
37215
38130
  column: location.column
37216
38131
  });
37217
38132
  }
38133
+ function warnIfSchema30Feature(sourceSchemaVersion, diagnostics, featureName, location) {
38134
+ if (!isSchemaOlderThan(sourceSchemaVersion, "3.0")) {
38135
+ return;
38136
+ }
38137
+ diagnostics.push({
38138
+ code: "parse.schema30.featureCompatibility",
38139
+ severity: "warning",
38140
+ source: "parse",
38141
+ message: `Feature "${featureName}" requires schema 3.0; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
38142
+ line: location.line,
38143
+ column: location.column
38144
+ });
38145
+ }
38146
+ function warnIfSchema31Feature(sourceSchemaVersion, diagnostics, featureName, location) {
38147
+ if (!isSchemaOlderThan(sourceSchemaVersion, "3.1")) {
38148
+ return;
38149
+ }
38150
+ diagnostics.push({
38151
+ code: "parse.schema31.featureCompatibility",
38152
+ severity: "warning",
38153
+ source: "parse",
38154
+ message: `Feature "${featureName}" requires schema 3.1; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
38155
+ line: location.line,
38156
+ column: location.column
38157
+ });
38158
+ }
37218
38159
  function isSchemaOlderThan(sourceSchemaVersion, requiredVersion) {
37219
38160
  return schemaVersionRank(sourceSchemaVersion) < schemaVersionRank(requiredVersion);
37220
38161
  }
@@ -37230,8 +38171,12 @@ void main() {
37230
38171
  return 3;
37231
38172
  case "2.6":
37232
38173
  return 4;
37233
- default:
38174
+ case "3.0":
37234
38175
  return 5;
38176
+ case "3.1":
38177
+ return 6;
38178
+ default:
38179
+ return 7;
37235
38180
  }
37236
38181
  }
37237
38182
  function preprocessAtlasSource(source) {
@@ -37321,7 +38266,7 @@ void main() {
37321
38266
  }
37322
38267
 
37323
38268
  // packages/core/dist/atlas-edit.js
37324
- function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.6") {
38269
+ function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "3.0") {
37325
38270
  return {
37326
38271
  format: "worldorbit",
37327
38272
  version,
@@ -37352,6 +38297,7 @@ void main() {
37352
38297
  groups: [],
37353
38298
  relations: [],
37354
38299
  events: [],
38300
+ trajectories: [],
37355
38301
  objects: [],
37356
38302
  diagnostics: []
37357
38303
  };
@@ -37384,6 +38330,19 @@ void main() {
37384
38330
  paths.push({ kind: "event-pose", id: event.id, key: pose.objectId });
37385
38331
  }
37386
38332
  }
38333
+ for (const trajectory of [...document2.trajectories].sort(compareIdLike2)) {
38334
+ paths.push({ kind: "trajectory", id: trajectory.id });
38335
+ for (const segment of [...trajectory.segments].sort(compareIdLike2)) {
38336
+ paths.push({ kind: "trajectory-segment", id: trajectory.id, key: segment.id });
38337
+ for (const maneuver of [...segment.maneuvers].sort(compareIdLike2)) {
38338
+ paths.push({
38339
+ kind: "trajectory-maneuver",
38340
+ id: trajectory.id,
38341
+ key: `${segment.id}:${maneuver.id}`
38342
+ });
38343
+ }
38344
+ }
38345
+ }
37387
38346
  for (const object of [...document2.objects].sort(compareIdLike2)) {
37388
38347
  paths.push({ kind: "object", id: object.id });
37389
38348
  }
@@ -37403,6 +38362,12 @@ void main() {
37403
38362
  return path.id ? findEvent(document2, path.id) : null;
37404
38363
  case "event-pose":
37405
38364
  return path.id && path.key ? findEventPose(document2, path.id, path.key) : null;
38365
+ case "trajectory":
38366
+ return path.id ? findTrajectory(document2, path.id) : null;
38367
+ case "trajectory-segment":
38368
+ return path.id && path.key ? findTrajectorySegment2(document2, path.id, path.key) : null;
38369
+ case "trajectory-maneuver":
38370
+ return path.id && path.key ? findTrajectoryManeuver2(document2, path.id, path.key) : null;
37406
38371
  case "object":
37407
38372
  return path.id ? findObject(document2, path.id) : null;
37408
38373
  case "viewpoint":
@@ -37454,6 +38419,24 @@ void main() {
37454
38419
  }
37455
38420
  upsertEventPose(next.events, path.id, value);
37456
38421
  return next;
38422
+ case "trajectory":
38423
+ if (!path.id) {
38424
+ throw new Error('Trajectory updates require an "id" value.');
38425
+ }
38426
+ upsertById(next.trajectories, value);
38427
+ return next;
38428
+ case "trajectory-segment":
38429
+ if (!path.id || !path.key) {
38430
+ throw new Error('Trajectory segment updates require a trajectory "id" and segment "key" value.');
38431
+ }
38432
+ upsertTrajectorySegment(next.trajectories, path.id, value);
38433
+ return next;
38434
+ case "trajectory-maneuver":
38435
+ if (!path.id || !path.key) {
38436
+ throw new Error('Trajectory maneuver updates require a trajectory "id" and maneuver "key" value.');
38437
+ }
38438
+ upsertTrajectoryManeuver(next.trajectories, path.id, path.key, value);
38439
+ return next;
37457
38440
  case "object":
37458
38441
  if (!path.id) {
37459
38442
  throw new Error('Object updates require an "id" value.');
@@ -37515,6 +38498,30 @@ void main() {
37515
38498
  }
37516
38499
  }
37517
38500
  return next;
38501
+ case "trajectory":
38502
+ if (path.id) {
38503
+ next.trajectories = next.trajectories.filter((trajectory) => trajectory.id !== path.id);
38504
+ }
38505
+ return next;
38506
+ case "trajectory-segment":
38507
+ if (path.id && path.key) {
38508
+ const trajectory = findTrajectory(next, path.id);
38509
+ if (trajectory) {
38510
+ trajectory.segments = trajectory.segments.filter((segment) => segment.id !== path.key);
38511
+ }
38512
+ }
38513
+ return next;
38514
+ case "trajectory-maneuver":
38515
+ if (path.id && path.key) {
38516
+ const maneuver = splitTrajectoryManeuverKey(path.key);
38517
+ if (maneuver) {
38518
+ const segment = findTrajectorySegment2(next, path.id, maneuver.segmentId);
38519
+ if (segment) {
38520
+ segment.maneuvers = segment.maneuvers.filter((entry) => entry.id !== maneuver.maneuverId);
38521
+ }
38522
+ }
38523
+ }
38524
+ return next;
37518
38525
  case "viewpoint":
37519
38526
  if (path.id) {
37520
38527
  system.viewpoints = system.viewpoints.filter((viewpoint) => viewpoint.id !== path.id);
@@ -37599,6 +38606,29 @@ void main() {
37599
38606
  };
37600
38607
  }
37601
38608
  }
38609
+ if (diagnostic.field?.startsWith("trajectory.")) {
38610
+ const parts = diagnostic.field.split(".");
38611
+ if (parts[1] && findTrajectory(document2, parts[1])) {
38612
+ if (parts[2] === "segment" && parts[3] && findTrajectorySegment2(document2, parts[1], parts[3])) {
38613
+ if (parts[4] === "maneuver" && parts[5] && findTrajectoryManeuver2(document2, parts[1], `${parts[3]}:${parts[5]}`)) {
38614
+ return {
38615
+ kind: "trajectory-maneuver",
38616
+ id: parts[1],
38617
+ key: `${parts[3]}:${parts[5]}`
38618
+ };
38619
+ }
38620
+ return {
38621
+ kind: "trajectory-segment",
38622
+ id: parts[1],
38623
+ key: parts[3]
38624
+ };
38625
+ }
38626
+ return {
38627
+ kind: "trajectory",
38628
+ id: parts[1]
38629
+ };
38630
+ }
38631
+ }
37602
38632
  if (diagnostic.field && diagnostic.field in ensureSystem(document2).atlasMetadata) {
37603
38633
  return {
37604
38634
  kind: "metadata",
@@ -37636,6 +38666,19 @@ void main() {
37636
38666
  function findEventPose(document2, eventId, objectId) {
37637
38667
  return findEvent(document2, eventId)?.positions.find((pose) => pose.objectId === objectId) ?? null;
37638
38668
  }
38669
+ function findTrajectory(document2, trajectoryId) {
38670
+ return document2.trajectories.find((trajectory) => trajectory.id === trajectoryId) ?? null;
38671
+ }
38672
+ function findTrajectorySegment2(document2, trajectoryId, segmentId) {
38673
+ return findTrajectory(document2, trajectoryId)?.segments.find((segment) => segment.id === segmentId) ?? null;
38674
+ }
38675
+ function findTrajectoryManeuver2(document2, trajectoryId, combinedKey) {
38676
+ const parsed = splitTrajectoryManeuverKey(combinedKey);
38677
+ if (!parsed) {
38678
+ return null;
38679
+ }
38680
+ return findTrajectorySegment2(document2, trajectoryId, parsed.segmentId)?.maneuvers.find((maneuver) => maneuver.id === parsed.maneuverId) ?? null;
38681
+ }
37639
38682
  function findViewpoint(system, viewpointId) {
37640
38683
  return system?.viewpoints.find((viewpoint) => viewpoint.id === viewpointId) ?? null;
37641
38684
  }
@@ -37664,6 +38707,50 @@ void main() {
37664
38707
  }
37665
38708
  event.positions[index] = value;
37666
38709
  }
38710
+ function upsertTrajectorySegment(trajectories, trajectoryId, value) {
38711
+ const trajectory = trajectories.find((entry) => entry.id === trajectoryId);
38712
+ if (!trajectory) {
38713
+ throw new Error(`Unknown trajectory "${trajectoryId}" for segment update.`);
38714
+ }
38715
+ const index = trajectory.segments.findIndex((entry) => entry.id === value.id);
38716
+ if (index === -1) {
38717
+ trajectory.segments.push(value);
38718
+ trajectory.segments.sort(compareIdLike2);
38719
+ return;
38720
+ }
38721
+ trajectory.segments[index] = value;
38722
+ }
38723
+ function upsertTrajectoryManeuver(trajectories, trajectoryId, combinedKey, value) {
38724
+ const parsed = splitTrajectoryManeuverKey(combinedKey);
38725
+ if (!parsed) {
38726
+ throw new Error(`Invalid trajectory maneuver key "${combinedKey}".`);
38727
+ }
38728
+ const trajectory = trajectories.find((entry) => entry.id === trajectoryId);
38729
+ if (!trajectory) {
38730
+ throw new Error(`Unknown trajectory "${trajectoryId}" for maneuver update.`);
38731
+ }
38732
+ const segment = trajectory.segments.find((entry) => entry.id === parsed.segmentId);
38733
+ if (!segment) {
38734
+ throw new Error(`Unknown trajectory segment "${parsed.segmentId}" on "${trajectoryId}".`);
38735
+ }
38736
+ const index = segment.maneuvers.findIndex((entry) => entry.id === value.id);
38737
+ if (index === -1) {
38738
+ segment.maneuvers.push(value);
38739
+ segment.maneuvers.sort(compareIdLike2);
38740
+ return;
38741
+ }
38742
+ segment.maneuvers[index] = value;
38743
+ }
38744
+ function splitTrajectoryManeuverKey(key) {
38745
+ const separator = key.indexOf(":");
38746
+ if (separator <= 0 || separator >= key.length - 1) {
38747
+ return null;
38748
+ }
38749
+ return {
38750
+ segmentId: key.slice(0, separator),
38751
+ maneuverId: key.slice(separator + 1)
38752
+ };
38753
+ }
37667
38754
  function compareIdLike2(left, right) {
37668
38755
  return left.id.localeCompare(right.id);
37669
38756
  }
@@ -37672,10 +38759,12 @@ void main() {
37672
38759
  }
37673
38760
 
37674
38761
  // packages/core/dist/load.js
37675
- var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1|\.5|\.6)?$/i;
38762
+ var ATLAS_SCHEMA_PATTERN = /^schema\s+(?:2(?:\.0|\.1|\.5|\.6)?|3(?:\.0|\.1)?)$/i;
37676
38763
  var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
37677
38764
  var ATLAS_SCHEMA_25_PATTERN = /^schema\s+2\.5$/i;
37678
38765
  var ATLAS_SCHEMA_26_PATTERN = /^schema\s+2\.6$/i;
38766
+ var ATLAS_SCHEMA_30_PATTERN = /^schema\s+3(?:\.0)?$/i;
38767
+ var ATLAS_SCHEMA_31_PATTERN = /^schema\s+3\.1$/i;
37679
38768
  var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
37680
38769
  function detectWorldOrbitSchemaVersion(source) {
37681
38770
  for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
@@ -37695,6 +38784,12 @@ void main() {
37695
38784
  if (ATLAS_SCHEMA_26_PATTERN.test(trimmed)) {
37696
38785
  return "2.6";
37697
38786
  }
38787
+ if (ATLAS_SCHEMA_30_PATTERN.test(trimmed)) {
38788
+ return "3.0";
38789
+ }
38790
+ if (ATLAS_SCHEMA_31_PATTERN.test(trimmed)) {
38791
+ return "3.1";
38792
+ }
37698
38793
  if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
37699
38794
  return "2.0";
37700
38795
  }
@@ -37755,7 +38850,7 @@ void main() {
37755
38850
  }
37756
38851
  function loadWorldOrbitSourceWithDiagnostics(source) {
37757
38852
  const schemaVersion = detectWorldOrbitSchemaVersion(source);
37758
- if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5" || schemaVersion === "2.6") {
38853
+ if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5" || schemaVersion === "2.6" || schemaVersion === "3.0" || schemaVersion === "3.1") {
37759
38854
  return loadAtlasSourceWithDiagnostics(source, schemaVersion);
37760
38855
  }
37761
38856
  let ast;
@@ -37928,6 +39023,7 @@ void main() {
37928
39023
  relations: true,
37929
39024
  events: true,
37930
39025
  orbits: true,
39026
+ trajectories: true,
37931
39027
  objects: true,
37932
39028
  labels: true,
37933
39029
  structures: true,
@@ -38469,6 +39565,11 @@ void main() {
38469
39565
  const orbitMarkup = layers.orbits ? renderOrbitLayer(scene, visibleObjectIds, layers.structures) : { back: "", front: "" };
38470
39566
  const leaderMarkup = layers.guides ? scene.leaders.filter((leader) => !leader.hidden).filter((leader) => visibleObjectIds.has(leader.objectId)).filter((leader) => layers.structures || !isStructureLike(leader.object)).map((leader) => `<line class="wo-leader wo-leader-${leader.mode}" x1="${leader.x1}" y1="${leader.y1}" x2="${leader.x2}" y2="${leader.y2}" data-render-id="${escapeXml(leader.renderId)}" data-group-id="${escapeAttribute(leader.groupId ?? "")}" />`).join("") : "";
38471
39567
  const relationMarkup = layers.relations ? scene.relations.filter((relation) => !relation.hidden).filter((relation) => visibleObjectIds.has(relation.fromObjectId) && visibleObjectIds.has(relation.toObjectId)).map((relation) => `<line class="wo-relation" x1="${relation.x1}" y1="${relation.y1}" x2="${relation.x2}" y2="${relation.y2}" data-render-id="${escapeXml(relation.renderId)}" data-relation-id="${escapeAttribute(relation.relationId)}" />`).join("") : "";
39568
+ const trajectoryMarkup = layers.trajectories ? renderTrajectoryLayer(scene, visibleObjectIds, {
39569
+ showLabels: options.showTrajectoryLabels ?? true,
39570
+ showWaypoints: options.showTrajectoryWaypoints ?? true,
39571
+ includeStructures: layers.structures
39572
+ }) : "";
38472
39573
  const eventMarkup = layers.events ? scene.events.filter((event) => !event.hidden).map((event) => renderSceneEventOverlay(scene, event, visibleObjectIds, theme)).join("") : "";
38473
39574
  const objectMarkup = layers.objects ? visibleObjects.map((object) => renderSceneObject(object, options.selectedObjectId ?? null, theme)).join("") : "";
38474
39575
  const labelMarkup = layers.labels ? visibleLabels.map((label) => renderSceneLabel(scene, label, options.selectedObjectId ?? null)).join("") : "";
@@ -38494,6 +39595,9 @@ void main() {
38494
39595
  <stop offset="0%" stop-color="${theme.backgroundGlow}" />
38495
39596
  <stop offset="100%" stop-color="transparent" />
38496
39597
  </radialGradient>
39598
+ <marker id="wo-trajectory-arrow" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto" markerUnits="strokeWidth">
39599
+ <path d="M 0 0 L 12 6 L 0 12 z" fill="${theme.accent}" />
39600
+ </marker>
38497
39601
  ${imageDefinitions}
38498
39602
  </defs>
38499
39603
  <style>
@@ -38505,6 +39609,10 @@ void main() {
38505
39609
  .wo-orbit-front { opacity: 0.9; }
38506
39610
  .wo-orbit-band { stroke: ${theme.orbitBand}; stroke-linecap: round; }
38507
39611
  .wo-relation { stroke: ${theme.relation}; stroke-width: 2; stroke-dasharray: 10 6; }
39612
+ .wo-trajectory { fill: none; stroke: ${theme.accent}; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; opacity: 0.92; }
39613
+ .wo-trajectory-waypoint { fill: ${theme.spaceFog}; stroke: ${theme.selected}; stroke-width: 1.4; }
39614
+ .wo-trajectory-label { fill: ${theme.accent}; font-family: ${theme.fontFamily}; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
39615
+ .wo-trajectory-date { fill: ${theme.muted}; font-family: ${theme.fontFamily}; font-weight: 500; }
38508
39616
  .wo-event-line { stroke: ${theme.accent}; stroke-width: 1.6; stroke-dasharray: 5 5; opacity: 0.72; }
38509
39617
  .wo-event-node { fill: ${theme.accent}; stroke: ${theme.selected}; stroke-width: 1.4; opacity: 0.92; }
38510
39618
  .wo-event-label { fill: ${theme.accent}; font-family: ${theme.fontFamily}; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
@@ -38545,6 +39653,7 @@ void main() {
38545
39653
  ${layers.events ? `<g data-layer-id="events">${eventMarkup}</g>` : ""}
38546
39654
  ${layers.objects ? `<g data-layer-id="objects">${objectMarkup}</g>` : ""}
38547
39655
  ${layers.orbits ? `<g data-layer-id="orbits-front">${orbitMarkup.front}</g>` : ""}
39656
+ ${layers.trajectories ? `<g data-layer-id="trajectories">${trajectoryMarkup}</g>` : ""}
38548
39657
  ${layers.labels ? `<g data-layer-id="labels">${labelMarkup}</g>` : ""}
38549
39658
  </g>
38550
39659
  </g>
@@ -38565,6 +39674,30 @@ void main() {
38565
39674
  <text class="wo-event-label" x="${event.x}" y="${event.y - 10}" text-anchor="middle" font-size="10">${escapeXml(label)}</text>
38566
39675
  </g>`;
38567
39676
  }
39677
+ function renderTrajectoryLayer(scene, visibleObjectIds, options) {
39678
+ return scene.trajectories.filter((trajectory) => !trajectory.hidden).filter((trajectory) => trajectory.objectIds.length === 0 || trajectory.objectIds.some((objectId) => visibleObjectIds.has(objectId))).filter((trajectory) => options.includeStructures || !trajectory.objectIds.some((objectId) => {
39679
+ const object = scene.objects.find((entry) => entry.objectId === objectId)?.object;
39680
+ return object ? isStructureLike(object) : false;
39681
+ })).map((trajectory) => {
39682
+ const stroke = trajectory.stroke ?? "#f0b464";
39683
+ const markerEnd = trajectory.marker === "none" ? "" : ` marker-end="url(#wo-trajectory-arrow)"`;
39684
+ const waypointMarkup = trajectory.showWaypoints && options.showWaypoints ? trajectory.waypoints.filter((waypoint) => !waypoint.hidden).map((waypoint) => renderTrajectoryWaypoint(trajectory, waypoint, options.showLabels)).join("") : "";
39685
+ return `<g class="wo-trajectory-group" data-render-id="${escapeXml(trajectory.renderId)}" data-trajectory-id="${escapeAttribute(trajectory.trajectoryId)}">
39686
+ <path class="wo-trajectory wo-trajectory-${trajectory.mode}" d="${trajectory.path}" stroke="${escapeAttribute(stroke)}" stroke-width="${trajectory.strokeWidth}"${markerEnd} />
39687
+ ${waypointMarkup}
39688
+ </g>`;
39689
+ }).join("");
39690
+ }
39691
+ function renderTrajectoryWaypoint(trajectory, waypoint, showLabels) {
39692
+ const labelMarkup = showLabels && trajectory.labelMode !== "hidden" ? [
39693
+ waypoint.label ? `<text class="wo-trajectory-label" x="${waypoint.x + 10}" y="${waypoint.y - 10}" font-size="10">${escapeXml(waypoint.label)}</text>` : "",
39694
+ waypoint.dateLabel ? `<text class="wo-trajectory-date" x="${waypoint.x + 10}" y="${waypoint.y + 4}" font-size="9">${escapeXml(waypoint.dateLabel)}</text>` : ""
39695
+ ].join("") : "";
39696
+ return `<g class="wo-trajectory-waypoint-group" data-render-id="${escapeXml(waypoint.renderId)}" data-trajectory-id="${escapeAttribute(waypoint.trajectoryId)}">
39697
+ <circle class="wo-trajectory-waypoint" cx="${waypoint.x}" cy="${waypoint.y}" r="4.5" />
39698
+ ${labelMarkup}
39699
+ </g>`;
39700
+ }
38568
39701
  function renderDocumentToSvg(document2, options = {}) {
38569
39702
  return renderSceneToSvg(renderDocumentToScene(document2, options), options);
38570
39703
  }
@@ -38653,6 +39786,8 @@ void main() {
38653
39786
  case "comet":
38654
39787
  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
39788
  <circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
39789
+ case "craft":
39790
+ return `<polygon points="${diamondPoints(x, y, radius * 0.85)}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
38656
39791
  case "structure":
38657
39792
  return `<polygon points="${diamondPoints(x, y, radius)}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
38658
39793
  case "phenomenon": {
@@ -38786,6 +39921,8 @@ void main() {
38786
39921
  return { fill: "#9ce7ff", stroke: "#e7fbff" };
38787
39922
  case "ring":
38788
39923
  return { fill: "#e59f7d", stroke: "#fff0d3" };
39924
+ case "craft":
39925
+ return { fill: "#ffb47f", stroke: "#fff0d3" };
38789
39926
  case "structure":
38790
39927
  return { fill: theme.accentStrong, stroke: "#fff2ea" };
38791
39928
  case "phenomenon":
@@ -39051,6 +40188,8 @@ void main() {
39051
40188
  return "#a7a5b8";
39052
40189
  case "comet":
39053
40190
  return "#9ce7ff";
40191
+ case "craft":
40192
+ return "#ffb47f";
39054
40193
  case "structure":
39055
40194
  return "#ff7f5f";
39056
40195
  case "phenomenon":
@@ -39709,6 +40848,8 @@ void main() {
39709
40848
  return "#b8926a";
39710
40849
  case "ring":
39711
40850
  return "#cdbf9a";
40851
+ case "craft":
40852
+ return "#ffce8a";
39712
40853
  case "structure":
39713
40854
  return "#ffce8a";
39714
40855
  case "phenomenon":
@@ -40965,7 +42106,7 @@ void main() {
40965
42106
  tooltipRoot.hidden = false;
40966
42107
  tooltipRoot.dataset.mode = resolved.mode;
40967
42108
  tooltipRoot.classList.toggle("is-pinned", resolved.mode === "pinned");
40968
- tooltipRoot.style.pointerEvents = "auto";
42109
+ tooltipRoot.style.pointerEvents = resolved.mode === "pinned" ? "auto" : "none";
40969
42110
  tooltipRoot.style.visibility = "hidden";
40970
42111
  renderTooltipContent(tooltipRoot, tooltipDetails, resolved.mode);
40971
42112
  positionTooltip(tooltipRoot, details.renderObject);
@@ -41517,6 +42658,33 @@ void main() {
41517
42658
  hidden: orbit.hidden,
41518
42659
  motion: null
41519
42660
  })),
42661
+ trajectories: scene.trajectories.map((trajectory) => ({
42662
+ trajectoryId: trajectory.trajectoryId,
42663
+ trajectory: trajectory.trajectory,
42664
+ craftObjectId: trajectory.craftObjectId,
42665
+ mode: trajectory.mode,
42666
+ stroke: trajectory.stroke,
42667
+ strokeWidth: trajectory.strokeWidth,
42668
+ marker: trajectory.marker,
42669
+ labelMode: trajectory.labelMode,
42670
+ showWaypoints: trajectory.showWaypoints,
42671
+ samples: [],
42672
+ waypoints: trajectory.waypoints.map((waypoint) => ({
42673
+ trajectoryId: waypoint.trajectoryId,
42674
+ segmentId: waypoint.segmentId,
42675
+ maneuverId: waypoint.maneuverId,
42676
+ objectId: waypoint.objectId,
42677
+ position: {
42678
+ x: waypoint.x - scene.contentBounds.centerX,
42679
+ y: 0,
42680
+ z: waypoint.y - scene.contentBounds.centerY
42681
+ },
42682
+ label: waypoint.label,
42683
+ dateLabel: waypoint.dateLabel,
42684
+ hidden: waypoint.hidden
42685
+ })),
42686
+ hidden: trajectory.hidden
42687
+ })),
41520
42688
  focusTargets: scene.objects.map((object) => ({
41521
42689
  objectId: object.objectId,
41522
42690
  center: {
@@ -41558,7 +42726,11 @@ void main() {
41558
42726
  if (!(target instanceof Element)) {
41559
42727
  return null;
41560
42728
  }
41561
- return target.closest("[data-object-id]")?.dataset.objectId ?? null;
42729
+ const selectionTarget = target.closest("[data-object-id], [data-orbit-object-id]");
42730
+ if (!selectionTarget) {
42731
+ return null;
42732
+ }
42733
+ return selectionTarget.dataset.objectId ?? selectionTarget.dataset.orbitObjectId ?? null;
41562
42734
  }
41563
42735
  function ensureBrowserEnvironment(container) {
41564
42736
  if (typeof window === "undefined" || typeof document === "undefined") {
@@ -41690,7 +42862,7 @@ void main() {
41690
42862
  backdrop-filter: blur(12px);
41691
42863
  font: 500 13px/1.5 "Segoe UI Variable", "Segoe UI", sans-serif;
41692
42864
  }
41693
- .wo-viewer-tooltip-root[data-mode="hover"] { pointer-events: auto; }
42865
+ .wo-viewer-tooltip-root[data-mode="hover"] { pointer-events: none; }
41694
42866
  .wo-viewer-tooltip-root[data-mode="pinned"] { pointer-events: auto; }
41695
42867
  .wo-tooltip-card { display: grid; gap: 10px; }
41696
42868
  .wo-tooltip-head { display: grid; grid-template-columns: 52px minmax(0, 1fr); gap: 12px; align-items: center; }