worldorbit 2.5.17 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -921,7 +921,9 @@
921
921
  const height = frame.height;
922
922
  const padding = frame.padding;
923
923
  const layoutPreset = resolveLayoutPreset(document2);
924
- const projection = resolveProjection(document2, options.projection);
924
+ const schemaProjection = resolveProjection(document2, options.projection);
925
+ const camera = normalizeViewCamera(options.camera ?? null);
926
+ const renderProjection = resolveRenderProjection(schemaProjection, camera);
925
927
  const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
926
928
  const spacingFactor = layoutPresetSpacing(layoutPreset);
927
929
  const systemId = document2.system?.id ?? null;
@@ -964,7 +966,7 @@
964
966
  surfaceChildren,
965
967
  objectMap,
966
968
  spacingFactor,
967
- projection,
969
+ projection: renderProjection,
968
970
  scaleModel
969
971
  };
970
972
  const primaryRoot = rootObjects.find((object) => object.type === "star") ?? rootObjects[0] ?? null;
@@ -976,7 +978,7 @@
976
978
  const rootRingRadius = Math.min(width, height) * 0.28 * spacingFactor * scaleModel.orbitDistanceMultiplier;
977
979
  secondaryRoots.forEach((object, index) => {
978
980
  const angle = angleForIndex(index, secondaryRoots.length, -Math.PI / 2);
979
- const offset = projectPolarOffset(angle, rootRingRadius, projection, 1);
981
+ const offset = projectPolarOffset(angle, rootRingRadius, renderProjection, 1);
980
982
  placeObject(object, centerX + offset.x, centerY + offset.y, 0, positions, orbitDrafts, leaderDrafts, context);
981
983
  });
982
984
  }
@@ -1038,27 +1040,34 @@
1038
1040
  const layers = createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels);
1039
1041
  const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, scaleModel.labelMultiplier);
1040
1042
  const semanticGroups = createSceneSemanticGroups(document2, objects);
1041
- const viewpoints = createSceneViewpoints(document2, projection, frame.preset, relationships, objectMap);
1043
+ const viewpoints = createSceneViewpoints(document2, schemaProjection, frame.preset, relationships, objectMap);
1042
1044
  const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, scaleModel.labelMultiplier);
1043
1045
  return {
1044
1046
  width,
1045
1047
  height,
1046
1048
  padding,
1047
1049
  renderPreset: frame.preset,
1048
- projection,
1050
+ projection: schemaProjection,
1051
+ renderProjection,
1052
+ camera,
1049
1053
  scaleModel,
1050
1054
  title: String(document2.system?.title ?? document2.system?.properties.title ?? document2.system?.id ?? "WorldOrbit") || "WorldOrbit",
1051
- subtitle: `${capitalizeLabel(projection)} view - ${capitalizeLabel(layoutPreset)} layout`,
1055
+ subtitle: buildSceneSubtitle(schemaProjection, renderProjection, layoutPreset, camera),
1052
1056
  systemId,
1053
- viewMode: projection,
1057
+ viewMode: schemaProjection,
1054
1058
  layoutPreset,
1055
1059
  metadata: {
1056
1060
  format: document2.format,
1057
1061
  version: document2.version,
1058
- view: projection,
1062
+ view: schemaProjection,
1063
+ renderProjection,
1059
1064
  scale: String(document2.system?.properties.scale ?? layoutPreset),
1060
1065
  units: String(document2.system?.properties.units ?? "mixed"),
1061
- preset: frame.preset ?? "custom"
1066
+ preset: frame.preset ?? "custom",
1067
+ ...camera?.azimuth !== null ? { "camera.azimuth": String(camera?.azimuth) } : {},
1068
+ ...camera?.elevation !== null ? { "camera.elevation": String(camera?.elevation) } : {},
1069
+ ...camera?.roll !== null ? { "camera.roll": String(camera?.roll) } : {},
1070
+ ...camera?.distance !== null ? { "camera.distance": String(camera?.distance) } : {}
1062
1071
  },
1063
1072
  contentBounds,
1064
1073
  layers,
@@ -1084,21 +1093,42 @@
1084
1093
  return cloned;
1085
1094
  }
1086
1095
  const objectMap = new Map(cloned.map((object) => [object.id, object]));
1096
+ const referencedIds = /* @__PURE__ */ new Set([
1097
+ ...activeEvent.targetObjectId ? [activeEvent.targetObjectId] : [],
1098
+ ...activeEvent.participantObjectIds,
1099
+ ...activeEvent.positions.map((pose) => pose.objectId)
1100
+ ]);
1101
+ for (const objectId of referencedIds) {
1102
+ const object = objectMap.get(objectId);
1103
+ if (!object) {
1104
+ continue;
1105
+ }
1106
+ if (activeEvent.epoch) {
1107
+ object.epoch = activeEvent.epoch;
1108
+ }
1109
+ if (activeEvent.referencePlane) {
1110
+ object.referencePlane = activeEvent.referencePlane;
1111
+ }
1112
+ }
1087
1113
  for (const pose of activeEvent.positions) {
1088
1114
  const object = objectMap.get(pose.objectId);
1089
1115
  if (!object) {
1090
1116
  continue;
1091
1117
  }
1092
- object.placement = pose.placement ? structuredClone(pose.placement) : null;
1118
+ if (pose.placement) {
1119
+ object.placement = structuredClone(pose.placement);
1120
+ }
1093
1121
  if (pose.inner) {
1094
1122
  object.properties.inner = { ...pose.inner };
1095
- } else {
1096
- delete object.properties.inner;
1097
1123
  }
1098
1124
  if (pose.outer) {
1099
1125
  object.properties.outer = { ...pose.outer };
1100
- } else {
1101
- delete object.properties.outer;
1126
+ }
1127
+ if (pose.epoch) {
1128
+ object.epoch = pose.epoch;
1129
+ }
1130
+ if (pose.referencePlane) {
1131
+ object.referencePlane = pose.referencePlane;
1102
1132
  }
1103
1133
  }
1104
1134
  return cloned;
@@ -1139,10 +1169,59 @@
1139
1169
  }
1140
1170
  }
1141
1171
  function resolveProjection(document2, projection) {
1142
- if (projection === "topdown" || projection === "isometric") {
1172
+ if (projection === "topdown" || projection === "isometric" || projection === "orthographic" || projection === "perspective") {
1143
1173
  return projection;
1144
1174
  }
1145
- return String(document2.system?.properties.view ?? "topdown").toLowerCase() === "isometric" ? "isometric" : "topdown";
1175
+ const documentView = String(document2.system?.properties.view ?? "topdown").toLowerCase();
1176
+ return parseViewProjection(documentView) ?? "topdown";
1177
+ }
1178
+ function resolveRenderProjection(projection, camera) {
1179
+ switch (projection) {
1180
+ case "topdown":
1181
+ return "topdown";
1182
+ case "isometric":
1183
+ return "isometric";
1184
+ case "orthographic":
1185
+ return camera && (camera.azimuth !== null || camera.elevation !== null || camera.roll !== null) ? "isometric" : "topdown";
1186
+ case "perspective":
1187
+ return "isometric";
1188
+ }
1189
+ }
1190
+ function normalizeViewCamera(camera) {
1191
+ if (!camera) {
1192
+ return null;
1193
+ }
1194
+ const normalized = {
1195
+ azimuth: normalizeFiniteCameraValue(camera.azimuth),
1196
+ elevation: normalizeFiniteCameraValue(camera.elevation),
1197
+ roll: normalizeFiniteCameraValue(camera.roll),
1198
+ distance: normalizePositiveCameraDistance(camera.distance)
1199
+ };
1200
+ return normalized.azimuth !== null || normalized.elevation !== null || normalized.roll !== null || normalized.distance !== null ? normalized : null;
1201
+ }
1202
+ function normalizeFiniteCameraValue(value) {
1203
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1204
+ }
1205
+ function normalizePositiveCameraDistance(value) {
1206
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
1207
+ }
1208
+ function buildSceneSubtitle(projection, renderProjection, layoutPreset, camera) {
1209
+ const parts = [`${capitalizeLabel(projection)} view`, `${capitalizeLabel(layoutPreset)} layout`];
1210
+ if (projection !== renderProjection) {
1211
+ parts.push(`2D ${renderProjection} fallback`);
1212
+ }
1213
+ if (camera) {
1214
+ const cameraParts = [
1215
+ camera.azimuth !== null ? `az ${camera.azimuth}` : null,
1216
+ camera.elevation !== null ? `el ${camera.elevation}` : null,
1217
+ camera.roll !== null ? `roll ${camera.roll}` : null,
1218
+ camera.distance !== null ? `dist ${camera.distance}` : null
1219
+ ].filter(Boolean);
1220
+ if (cameraParts.length > 0) {
1221
+ parts.push(`camera ${cameraParts.join(" / ")}`);
1222
+ }
1223
+ }
1224
+ return parts.join(" - ");
1146
1225
  }
1147
1226
  function resolveScaleModel(layoutPreset, overrides) {
1148
1227
  const defaults = defaultScaleModel(layoutPreset);
@@ -1578,6 +1657,8 @@
1578
1657
  function createGeneratedOverviewViewpoint(document2, projection, preset) {
1579
1658
  const title = document2.system?.title ?? document2.system?.properties.title;
1580
1659
  const label = title ? `${String(title)} Overview` : "Overview";
1660
+ const camera = normalizeViewCamera(null);
1661
+ const renderProjection = resolveRenderProjection(projection, camera);
1581
1662
  return {
1582
1663
  id: "overview",
1583
1664
  label,
@@ -1586,6 +1667,8 @@
1586
1667
  selectedObjectId: null,
1587
1668
  eventIds: [],
1588
1669
  projection,
1670
+ renderProjection,
1671
+ camera,
1589
1672
  preset,
1590
1673
  rotationDeg: 0,
1591
1674
  scale: null,
@@ -1635,6 +1718,30 @@
1635
1718
  case "angle":
1636
1719
  draft.rotationDeg = parseFiniteNumber(normalizedValue) ?? draft.rotationDeg ?? 0;
1637
1720
  return;
1721
+ case "camera.azimuth":
1722
+ draft.camera = {
1723
+ ...draft.camera ?? createEmptyViewCamera(),
1724
+ azimuth: parseFiniteNumber(normalizedValue)
1725
+ };
1726
+ return;
1727
+ case "camera.elevation":
1728
+ draft.camera = {
1729
+ ...draft.camera ?? createEmptyViewCamera(),
1730
+ elevation: parseFiniteNumber(normalizedValue)
1731
+ };
1732
+ return;
1733
+ case "camera.roll":
1734
+ draft.camera = {
1735
+ ...draft.camera ?? createEmptyViewCamera(),
1736
+ roll: parseFiniteNumber(normalizedValue)
1737
+ };
1738
+ return;
1739
+ case "camera.distance":
1740
+ draft.camera = {
1741
+ ...draft.camera ?? createEmptyViewCamera(),
1742
+ distance: parsePositiveNumber(normalizedValue)
1743
+ };
1744
+ return;
1638
1745
  case "zoom":
1639
1746
  case "scale":
1640
1747
  draft.scale = parsePositiveNumber(normalizedValue);
@@ -1674,6 +1781,9 @@
1674
1781
  const selectedObjectId = draft.select && objectMap.has(draft.select) ? draft.select : objectId;
1675
1782
  const filter = normalizeViewpointFilter(draft.filter);
1676
1783
  const label = draft.label?.trim() || humanizeIdentifier(draft.id);
1784
+ const resolvedProjection = draft.projection ?? projection;
1785
+ const camera = normalizeViewCamera(draft.camera ?? null);
1786
+ const renderProjection = resolveRenderProjection(resolvedProjection, camera);
1677
1787
  return {
1678
1788
  id: draft.id,
1679
1789
  label,
@@ -1681,7 +1791,9 @@
1681
1791
  objectId,
1682
1792
  selectedObjectId,
1683
1793
  eventIds: [...new Set(draft.eventIds ?? [])],
1684
- projection: draft.projection ?? projection,
1794
+ projection: resolvedProjection,
1795
+ renderProjection,
1796
+ camera,
1685
1797
  preset: draft.preset ?? preset,
1686
1798
  rotationDeg: draft.rotationDeg ?? 0,
1687
1799
  scale: draft.scale ?? null,
@@ -1698,6 +1810,14 @@
1698
1810
  groupIds: []
1699
1811
  };
1700
1812
  }
1813
+ function createEmptyViewCamera() {
1814
+ return {
1815
+ azimuth: null,
1816
+ elevation: null,
1817
+ roll: null,
1818
+ distance: null
1819
+ };
1820
+ }
1701
1821
  function normalizeViewpointFilter(filter) {
1702
1822
  if (!filter) {
1703
1823
  return null;
@@ -1711,7 +1831,18 @@
1711
1831
  return normalized.query || normalized.objectTypes.length > 0 || normalized.tags.length > 0 || normalized.groupIds.length > 0 ? normalized : null;
1712
1832
  }
1713
1833
  function parseViewProjection(value) {
1714
- return value.toLowerCase() === "isometric" ? "isometric" : value.toLowerCase() === "topdown" ? "topdown" : null;
1834
+ switch (value.toLowerCase()) {
1835
+ case "topdown":
1836
+ return "topdown";
1837
+ case "isometric":
1838
+ return "isometric";
1839
+ case "orthographic":
1840
+ return "orthographic";
1841
+ case "perspective":
1842
+ return "perspective";
1843
+ default:
1844
+ return null;
1845
+ }
1715
1846
  }
1716
1847
  function parseRenderPreset(value) {
1717
1848
  const normalized = value.toLowerCase();
@@ -1749,7 +1880,7 @@
1749
1880
  }
1750
1881
  function parseViewpointGroups(value, document2, relationships, objectMap) {
1751
1882
  return splitListValue(value).map((entry) => {
1752
- if (document2.schemaVersion === "2.1" || document2.groups.some((group) => group.id === entry)) {
1883
+ if (document2.schemaVersion === "2.1" || document2.schemaVersion === "2.5" || document2.groups.some((group) => group.id === entry)) {
1753
1884
  return entry;
1754
1885
  }
1755
1886
  if (entry.startsWith("wo-") && entry.endsWith("-group")) {
@@ -2581,7 +2712,9 @@
2581
2712
  objectId: pose.objectId,
2582
2713
  placement: clonePlacement(pose.placement),
2583
2714
  inner: pose.inner ? { ...pose.inner } : void 0,
2584
- outer: pose.outer ? { ...pose.outer } : void 0
2715
+ outer: pose.outer ? { ...pose.outer } : void 0,
2716
+ epoch: pose.epoch ?? null,
2717
+ referencePlane: pose.referencePlane ?? null
2585
2718
  };
2586
2719
  }
2587
2720
  function clonePlacement(placement) {
@@ -2596,21 +2729,42 @@
2596
2729
  return;
2597
2730
  }
2598
2731
  const objectMap = new Map(objects.map((object) => [object.id, object]));
2732
+ const referencedIds = /* @__PURE__ */ new Set([
2733
+ ...event.targetObjectId ? [event.targetObjectId] : [],
2734
+ ...event.participantObjectIds,
2735
+ ...event.positions.map((pose) => pose.objectId)
2736
+ ]);
2737
+ for (const objectId of referencedIds) {
2738
+ const object = objectMap.get(objectId);
2739
+ if (!object) {
2740
+ continue;
2741
+ }
2742
+ if (event.epoch) {
2743
+ object.epoch = event.epoch;
2744
+ }
2745
+ if (event.referencePlane) {
2746
+ object.referencePlane = event.referencePlane;
2747
+ }
2748
+ }
2599
2749
  for (const pose of event.positions) {
2600
2750
  const object = objectMap.get(pose.objectId);
2601
2751
  if (!object) {
2602
2752
  continue;
2603
2753
  }
2604
- object.placement = clonePlacement(pose.placement);
2754
+ if (pose.placement) {
2755
+ object.placement = clonePlacement(pose.placement);
2756
+ }
2605
2757
  if (pose.inner) {
2606
2758
  object.properties.inner = { ...pose.inner };
2607
- } else {
2608
- delete object.properties.inner;
2609
2759
  }
2610
2760
  if (pose.outer) {
2611
2761
  object.properties.outer = { ...pose.outer };
2612
- } else {
2613
- delete object.properties.outer;
2762
+ }
2763
+ if (pose.epoch) {
2764
+ object.epoch = pose.epoch;
2765
+ }
2766
+ if (pose.referencePlane) {
2767
+ object.referencePlane = pose.referencePlane;
2614
2768
  }
2615
2769
  }
2616
2770
  }
@@ -2686,6 +2840,18 @@
2686
2840
  if (viewpoint.rotationDeg !== 0) {
2687
2841
  info2[`${prefix}.rotation`] = String(viewpoint.rotationDeg);
2688
2842
  }
2843
+ if (viewpoint.camera?.azimuth !== null) {
2844
+ info2[`${prefix}.camera.azimuth`] = String(viewpoint.camera?.azimuth);
2845
+ }
2846
+ if (viewpoint.camera?.elevation !== null) {
2847
+ info2[`${prefix}.camera.elevation`] = String(viewpoint.camera?.elevation);
2848
+ }
2849
+ if (viewpoint.camera?.roll !== null) {
2850
+ info2[`${prefix}.camera.roll`] = String(viewpoint.camera?.roll);
2851
+ }
2852
+ if (viewpoint.camera?.distance !== null) {
2853
+ info2[`${prefix}.camera.distance`] = String(viewpoint.camera?.distance);
2854
+ }
2689
2855
  const serializedLayers = serializeViewpointLayers(viewpoint.layers);
2690
2856
  if (serializedLayers) {
2691
2857
  info2[`${prefix}.layers`] = serializedLayers;
@@ -2929,13 +3095,13 @@
2929
3095
  validateRelation(relation, objectMap, diagnostics);
2930
3096
  }
2931
3097
  for (const viewpoint of document2.system?.viewpoints ?? []) {
2932
- validateViewpoint(viewpoint.filter, viewpoint.events ?? [], groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpoint.id);
3098
+ validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap);
2933
3099
  }
2934
3100
  for (const object of document2.objects) {
2935
3101
  validateObject(object, document2.system, objectMap, groupIds, diagnostics);
2936
3102
  }
2937
3103
  for (const event of document2.events) {
2938
- validateEvent(event, objectMap, diagnostics);
3104
+ validateEvent(event, document2.system, objectMap, diagnostics);
2939
3105
  }
2940
3106
  return diagnostics;
2941
3107
  }
@@ -2954,21 +3120,24 @@
2954
3120
  diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
2955
3121
  }
2956
3122
  }
2957
- function validateViewpoint(filter, eventRefs, groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpointId) {
2958
- if (sourceSchemaVersion === "2.1") {
3123
+ function validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap) {
3124
+ const filter = viewpoint.filter;
3125
+ if (sourceSchemaVersion === "2.1" || sourceSchemaVersion === "2.5") {
2959
3126
  if (filter) {
2960
3127
  for (const groupId of filter.groupIds) {
2961
3128
  if (!groupIds.has(groupId)) {
2962
- diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.groups`));
3129
+ diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.groups`));
2963
3130
  }
2964
3131
  }
2965
3132
  }
2966
- for (const eventId of eventRefs) {
3133
+ for (const eventId of viewpoint.events ?? []) {
2967
3134
  if (!eventIds.has(eventId)) {
2968
- diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.events`));
3135
+ diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.events`));
2969
3136
  }
2970
3137
  }
2971
3138
  }
3139
+ validateProjection(viewpoint.projection, diagnostics, `viewpoint.${viewpoint.id}.projection`, viewpoint.id);
3140
+ validateCamera(viewpoint.camera, viewpoint.projection, viewpoint.rotationDeg, diagnostics, viewpoint.id, viewpoint.focusObjectId, viewpoint.selectedObjectId, filter, objectMap);
2972
3141
  }
2973
3142
  function validateObject(object, system, objectMap, groupIds, diagnostics) {
2974
3143
  const placement = object.placement;
@@ -2981,6 +3150,12 @@
2981
3150
  }
2982
3151
  }
2983
3152
  }
3153
+ if (typeof object.epoch === "string" && !object.epoch.trim()) {
3154
+ diagnostics.push(warn("validate.epoch.empty", `Object "${object.id}" defines an empty epoch string.`, object.id, "epoch"));
3155
+ }
3156
+ if (typeof object.referencePlane === "string" && !object.referencePlane.trim()) {
3157
+ diagnostics.push(warn("validate.referencePlane.empty", `Object "${object.id}" defines an empty reference plane string.`, object.id, "referencePlane"));
3158
+ }
2984
3159
  if (orbitPlacement) {
2985
3160
  if (!objectMap.has(orbitPlacement.target)) {
2986
3161
  diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
@@ -3052,12 +3227,18 @@
3052
3227
  }
3053
3228
  }
3054
3229
  }
3055
- function validateEvent(event, objectMap, diagnostics) {
3230
+ function validateEvent(event, system, objectMap, diagnostics) {
3056
3231
  const fieldPrefix = `event.${event.id}`;
3057
3232
  const referencedIds = /* @__PURE__ */ new Set();
3058
3233
  if (!event.kind.trim()) {
3059
3234
  diagnostics.push(error("validate.event.kind.required", `Event "${event.id}" is missing a "kind" value.`, void 0, `${fieldPrefix}.kind`));
3060
3235
  }
3236
+ if (typeof event.epoch === "string" && !event.epoch.trim()) {
3237
+ diagnostics.push(warn("validate.event.epoch.empty", `Event "${event.id}" defines an empty epoch string.`, void 0, `${fieldPrefix}.epoch`));
3238
+ }
3239
+ if (typeof event.referencePlane === "string" && !event.referencePlane.trim()) {
3240
+ diagnostics.push(warn("validate.event.referencePlane.empty", `Event "${event.id}" defines an empty reference plane string.`, void 0, `${fieldPrefix}.referencePlane`));
3241
+ }
3061
3242
  if (!event.targetObjectId && event.participantObjectIds.length === 0) {
3062
3243
  diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
3063
3244
  }
@@ -3104,10 +3285,14 @@
3104
3285
  if (!referencedIds.has(pose.objectId)) {
3105
3286
  diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
3106
3287
  }
3107
- validateEventPose(pose, object, objectMap, diagnostics, poseFieldPrefix, event.id);
3288
+ validateEventPose(pose, object, event, system, objectMap, diagnostics, poseFieldPrefix, event.id);
3289
+ }
3290
+ const missingPoseIds = [...referencedIds].filter((objectId) => !poseIds.has(objectId));
3291
+ if (event.positions.length > 0 && missingPoseIds.length > 0) {
3292
+ diagnostics.push(warn("validate.event.positions.partial", `Event "${event.id}" leaves ${missingPoseIds.length} referenced object(s) on their base placement.`, void 0, `${fieldPrefix}.positions`));
3108
3293
  }
3109
3294
  }
3110
- function validateEventPose(pose, object, objectMap, diagnostics, fieldPrefix, eventId) {
3295
+ function validateEventPose(pose, object, event, system, objectMap, diagnostics, fieldPrefix, eventId) {
3111
3296
  const placement = pose.placement;
3112
3297
  if (!placement) {
3113
3298
  diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
@@ -3120,6 +3305,15 @@
3120
3305
  if (placement.distance && placement.semiMajor) {
3121
3306
  diagnostics.push(error("validate.event.pose.orbit.distanceConflict", `Event "${eventId}" pose "${pose.objectId}" cannot declare both "distance" and "semiMajor".`, void 0, `${fieldPrefix}.distance`));
3122
3307
  }
3308
+ if (placement.phase && !resolveEffectiveEpoch(system, object, event, pose)) {
3309
+ diagnostics.push(warn("validate.event.pose.phase.epochMissing", `Event "${eventId}" pose "${pose.objectId}" sets "phase" without an effective epoch.`, void 0, `${fieldPrefix}.phase`));
3310
+ }
3311
+ if (placement.inclination && !resolveEffectiveReferencePlane(system, object, event, pose)) {
3312
+ diagnostics.push(warn("validate.event.pose.inclination.referencePlaneMissing", `Event "${eventId}" pose "${pose.objectId}" sets "inclination" without an effective reference plane.`, void 0, `${fieldPrefix}.inclination`));
3313
+ }
3314
+ if (placement.period && !massInSolar(objectMap.get(placement.target)?.properties.mass)) {
3315
+ diagnostics.push(warn("validate.event.pose.period.massMissing", `Event "${eventId}" pose "${pose.objectId}" sets "period" but its central mass cannot be derived.`, void 0, `${fieldPrefix}.period`));
3316
+ }
3123
3317
  return;
3124
3318
  }
3125
3319
  if (placement.mode === "surface") {
@@ -3254,6 +3448,52 @@
3254
3448
  return null;
3255
3449
  }
3256
3450
  }
3451
+ function validateProjection(projection, diagnostics, field, viewpointId) {
3452
+ if (projection !== "topdown" && projection !== "isometric" && projection !== "orthographic" && projection !== "perspective") {
3453
+ diagnostics.push(error("validate.viewpoint.projection.invalid", `Unknown projection "${String(projection)}" in viewpoint "${viewpointId}".`, void 0, field));
3454
+ }
3455
+ }
3456
+ function validateCamera(camera, projection, rotationDeg, diagnostics, viewpointId, focusObjectId, selectedObjectId, filter, objectMap) {
3457
+ if (!camera) {
3458
+ return;
3459
+ }
3460
+ const prefix = `viewpoint.${viewpointId}.camera`;
3461
+ for (const [key, value] of [
3462
+ ["azimuth", camera.azimuth],
3463
+ ["elevation", camera.elevation],
3464
+ ["roll", camera.roll],
3465
+ ["distance", camera.distance]
3466
+ ]) {
3467
+ if (value !== null && (!Number.isFinite(value) || key === "distance" && value <= 0)) {
3468
+ diagnostics.push(error("validate.viewpoint.camera.invalid", `Invalid camera ${key} "${String(value)}" in viewpoint "${viewpointId}".`, void 0, `${prefix}.${key}`));
3469
+ }
3470
+ }
3471
+ if (camera.distance !== null && projection !== "perspective") {
3472
+ diagnostics.push(warn("validate.viewpoint.camera.distance.partialEffect", `Camera "distance" only has a semantic effect in perspective viewpoints; "${viewpointId}" uses "${projection}".`, void 0, `${prefix}.distance`));
3473
+ }
3474
+ if (projection === "topdown" && (camera.elevation !== null || camera.roll !== null)) {
3475
+ diagnostics.push(warn("validate.viewpoint.camera.topdownPartial", `Camera elevation/roll on topdown viewpoint "${viewpointId}" are currently stored for future 3D use and only partially affect 2D rendering.`, void 0, prefix));
3476
+ }
3477
+ if (projection === "isometric" && camera.elevation !== null) {
3478
+ diagnostics.push(info("validate.viewpoint.camera.isometricStored", `Camera elevation on isometric viewpoint "${viewpointId}" is preserved semantically for future 3D rendering.`, void 0, `${prefix}.elevation`));
3479
+ }
3480
+ if (camera.azimuth !== null && camera.azimuth !== 0 && rotationDeg !== 0) {
3481
+ diagnostics.push(warn("validate.viewpoint.rotation.cameraOverlap", `Viewpoint "${viewpointId}" uses camera.azimuth; keep "rotation" only for 2D screen rotation to avoid ambiguity.`, void 0, `${prefix}.azimuth`));
3482
+ }
3483
+ const hasAnchor = focusObjectId !== null && objectMap.has(focusObjectId) || selectedObjectId !== null && objectMap.has(selectedObjectId) || !!filter;
3484
+ if (!hasAnchor) {
3485
+ diagnostics.push(info("validate.viewpoint.camera.anchorMissing", `Viewpoint "${viewpointId}" stores camera settings without a focus object, selection, or filter anchor.`, void 0, prefix));
3486
+ }
3487
+ }
3488
+ function resolveEffectiveEpoch(system, object, event, pose) {
3489
+ return normalizeOptionalContextString(pose?.epoch) ?? normalizeOptionalContextString(event?.epoch) ?? normalizeOptionalContextString(object.epoch) ?? normalizeOptionalContextString(system?.epoch) ?? null;
3490
+ }
3491
+ function resolveEffectiveReferencePlane(system, object, event, pose) {
3492
+ return normalizeOptionalContextString(pose?.referencePlane) ?? normalizeOptionalContextString(event?.referencePlane) ?? normalizeOptionalContextString(object.referencePlane) ?? normalizeOptionalContextString(system?.referencePlane) ?? null;
3493
+ }
3494
+ function normalizeOptionalContextString(value) {
3495
+ return typeof value === "string" && value.trim() ? value.trim() : null;
3496
+ }
3257
3497
  function toleranceForField(object, field) {
3258
3498
  const tolerance = object.tolerances?.find((entry) => entry.field === field)?.value;
3259
3499
  if (typeof tolerance === "number") {
@@ -3362,7 +3602,9 @@
3362
3602
  "surface",
3363
3603
  "free",
3364
3604
  "inner",
3365
- "outer"
3605
+ "outer",
3606
+ "epoch",
3607
+ "referencePlane"
3366
3608
  ]);
3367
3609
  function parseWorldOrbitAtlas(source) {
3368
3610
  return parseAtlasSource(source);
@@ -3404,7 +3646,7 @@
3404
3646
  if (!sawSchemaHeader) {
3405
3647
  sourceSchemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
3406
3648
  sawSchemaHeader = true;
3407
- if (prepared.comments.length > 0 && sourceSchemaVersion !== "2.1") {
3649
+ if (prepared.comments.length > 0 && isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
3408
3650
  diagnostics.push({
3409
3651
  code: "parse.schema21.commentCompatibility",
3410
3652
  severity: "warning",
@@ -3474,11 +3716,11 @@
3474
3716
  return document2;
3475
3717
  }
3476
3718
  function assertDraftSchemaHeader(tokens, line) {
3477
- if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1"].includes(tokens[1].value.toLowerCase())) {
3478
- throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
3719
+ if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1", "2.5"].includes(tokens[1].value.toLowerCase())) {
3720
+ throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", "schema 2.5", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
3479
3721
  }
3480
3722
  const version = tokens[1].value.toLowerCase();
3481
- return version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
3723
+ return version === "2.5" ? "2.5" : version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
3482
3724
  }
3483
3725
  function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
3484
3726
  const keyword = tokens[0]?.value.toLowerCase();
@@ -3498,6 +3740,8 @@
3498
3740
  return {
3499
3741
  kind: "defaults",
3500
3742
  system,
3743
+ sourceSchemaVersion,
3744
+ diagnostics,
3501
3745
  seenFields: /* @__PURE__ */ new Set()
3502
3746
  };
3503
3747
  case "atlas":
@@ -3590,6 +3834,7 @@
3590
3834
  preset: system.defaults.preset,
3591
3835
  zoom: null,
3592
3836
  rotationDeg: 0,
3837
+ camera: null,
3593
3838
  layers: {},
3594
3839
  filter: null
3595
3840
  };
@@ -3603,7 +3848,10 @@
3603
3848
  seenFields: /* @__PURE__ */ new Set(),
3604
3849
  inFilter: false,
3605
3850
  filterIndent: null,
3606
- seenFilterFields: /* @__PURE__ */ new Set()
3851
+ seenFilterFields: /* @__PURE__ */ new Set(),
3852
+ inCamera: false,
3853
+ cameraIndent: null,
3854
+ seenCameraFields: /* @__PURE__ */ new Set()
3607
3855
  };
3608
3856
  }
3609
3857
  function startAnnotationSection(tokens, line, system, annotationIds) {
@@ -3710,6 +3958,8 @@
3710
3958
  participantObjectIds: [],
3711
3959
  timing: null,
3712
3960
  visibility: null,
3961
+ epoch: null,
3962
+ referencePlane: null,
3713
3963
  tags: [],
3714
3964
  color: null,
3715
3965
  hidden: false,
@@ -3834,6 +4084,12 @@
3834
4084
  const value = joinFieldValue(tokens, line);
3835
4085
  switch (key) {
3836
4086
  case "view":
4087
+ if (isSchema25Projection(value)) {
4088
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "defaults.view", {
4089
+ line,
4090
+ column: tokens[0].column
4091
+ });
4092
+ }
3837
4093
  section.system.defaults.view = parseProjectionValue(value, line, tokens[0].column);
3838
4094
  return;
3839
4095
  case "scale":
@@ -3873,14 +4129,36 @@
3873
4129
  throw new WorldOrbitError(`Unknown atlas field "${tokens[0].value}"`, line, tokens[0].column);
3874
4130
  }
3875
4131
  function applyViewpointField2(section, indent, tokens, line) {
4132
+ if (section.inCamera && indent <= (section.cameraIndent ?? 0)) {
4133
+ section.inCamera = false;
4134
+ section.cameraIndent = null;
4135
+ }
3876
4136
  if (section.inFilter && indent <= (section.filterIndent ?? 0)) {
3877
4137
  section.inFilter = false;
3878
4138
  section.filterIndent = null;
3879
4139
  }
4140
+ if (section.inCamera) {
4141
+ applyViewpointCameraField(section, tokens, line);
4142
+ return;
4143
+ }
3880
4144
  if (section.inFilter) {
3881
4145
  applyViewpointFilterField(section, tokens, line);
3882
4146
  return;
3883
4147
  }
4148
+ if (tokens.length === 1 && tokens[0].value.toLowerCase() === "camera") {
4149
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
4150
+ line,
4151
+ column: tokens[0].column
4152
+ });
4153
+ if (section.seenFields.has("camera")) {
4154
+ throw new WorldOrbitError('Duplicate viewpoint field "camera"', line, tokens[0].column);
4155
+ }
4156
+ section.seenFields.add("camera");
4157
+ section.inCamera = true;
4158
+ section.cameraIndent = indent;
4159
+ section.viewpoint.camera = section.viewpoint.camera ?? createEmptyViewCamera2();
4160
+ return;
4161
+ }
3884
4162
  if (tokens.length === 1 && tokens[0].value.toLowerCase() === "filter") {
3885
4163
  if (section.seenFields.has("filter")) {
3886
4164
  throw new WorldOrbitError('Duplicate viewpoint field "filter"', line, tokens[0].column);
@@ -3906,6 +4184,12 @@
3906
4184
  section.viewpoint.selectedObjectId = value;
3907
4185
  return;
3908
4186
  case "projection":
4187
+ if (isSchema25Projection(value)) {
4188
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "projection", {
4189
+ line,
4190
+ column: tokens[0].column
4191
+ });
4192
+ }
3909
4193
  section.viewpoint.projection = parseProjectionValue(value, line, tokens[0].column);
3910
4194
  return;
3911
4195
  case "preset":
@@ -3917,6 +4201,13 @@
3917
4201
  case "rotation":
3918
4202
  section.viewpoint.rotationDeg = parseFiniteNumber2(value, line, tokens[0].column, "rotation");
3919
4203
  return;
4204
+ case "camera":
4205
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
4206
+ line,
4207
+ column: tokens[0].column
4208
+ });
4209
+ section.viewpoint.camera = parseInlineViewCamera(tokens.slice(1), line, section.viewpoint.camera);
4210
+ return;
3920
4211
  case "layers":
3921
4212
  section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line, section.sourceSchemaVersion, section.diagnostics);
3922
4213
  return;
@@ -3931,6 +4222,28 @@
3931
4222
  throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
3932
4223
  }
3933
4224
  }
4225
+ function applyViewpointCameraField(section, tokens, line) {
4226
+ const key = requireUniqueField(tokens, section.seenCameraFields, line);
4227
+ const value = joinFieldValue(tokens, line);
4228
+ const camera = section.viewpoint.camera ?? createEmptyViewCamera2();
4229
+ switch (key) {
4230
+ case "azimuth":
4231
+ camera.azimuth = parseFiniteNumber2(value, line, tokens[0].column, "camera.azimuth");
4232
+ break;
4233
+ case "elevation":
4234
+ camera.elevation = parseFiniteNumber2(value, line, tokens[0].column, "camera.elevation");
4235
+ break;
4236
+ case "roll":
4237
+ camera.roll = parseFiniteNumber2(value, line, tokens[0].column, "camera.roll");
4238
+ break;
4239
+ case "distance":
4240
+ camera.distance = parsePositiveNumber2(value, line, tokens[0].column, "camera.distance");
4241
+ break;
4242
+ default:
4243
+ throw new WorldOrbitError(`Unknown viewpoint camera field "${tokens[0].value}"`, line, tokens[0].column);
4244
+ }
4245
+ section.viewpoint.camera = camera;
4246
+ }
3934
4247
  function applyViewpointFilterField(section, tokens, line) {
3935
4248
  const key = requireUniqueField(tokens, section.seenFilterFields, line);
3936
4249
  const filter = section.viewpoint.filter ?? createEmptyViewpointFilter2();
@@ -4041,6 +4354,12 @@
4041
4354
  section.positionsIndent = null;
4042
4355
  }
4043
4356
  if (section.activePose) {
4357
+ if (tokens[0]?.value === "epoch" || tokens[0]?.value === "referencePlane") {
4358
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, `pose.${tokens[0].value}`, {
4359
+ line,
4360
+ column: tokens[0]?.column ?? 1
4361
+ });
4362
+ }
4044
4363
  section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
4045
4364
  return;
4046
4365
  }
@@ -4095,6 +4414,20 @@
4095
4414
  case "visibility":
4096
4415
  section.event.visibility = joinFieldValue(tokens, line);
4097
4416
  return;
4417
+ case "epoch":
4418
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.epoch", {
4419
+ line,
4420
+ column: tokens[0].column
4421
+ });
4422
+ section.event.epoch = joinFieldValue(tokens, line);
4423
+ return;
4424
+ case "referenceplane":
4425
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.referencePlane", {
4426
+ line,
4427
+ column: tokens[0].column
4428
+ });
4429
+ section.event.referencePlane = joinFieldValue(tokens, line);
4430
+ return;
4098
4431
  case "tags":
4099
4432
  section.event.tags = parseTokenList(tokens.slice(1), line, "tags");
4100
4433
  return;
@@ -4222,11 +4555,15 @@
4222
4555
  }
4223
4556
  function parseProjectionValue(value, line, column) {
4224
4557
  const normalized = value.toLowerCase();
4225
- if (normalized !== "topdown" && normalized !== "isometric") {
4558
+ if (normalized !== "topdown" && normalized !== "isometric" && normalized !== "orthographic" && normalized !== "perspective") {
4226
4559
  throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
4227
4560
  }
4228
4561
  return normalized;
4229
4562
  }
4563
+ function isSchema25Projection(value) {
4564
+ const normalized = value.toLowerCase();
4565
+ return normalized === "orthographic" || normalized === "perspective";
4566
+ }
4230
4567
  function parsePresetValue(value, line, column) {
4231
4568
  const normalized = value.toLowerCase();
4232
4569
  if (normalized === "diagram" || normalized === "presentation" || normalized === "atlas-card" || normalized === "markdown") {
@@ -4256,6 +4593,48 @@
4256
4593
  groupIds: []
4257
4594
  };
4258
4595
  }
4596
+ function createEmptyViewCamera2() {
4597
+ return {
4598
+ azimuth: null,
4599
+ elevation: null,
4600
+ roll: null,
4601
+ distance: null
4602
+ };
4603
+ }
4604
+ function parseInlineViewCamera(tokens, line, current) {
4605
+ if (tokens.length === 0 || tokens.length % 2 !== 0) {
4606
+ throw new WorldOrbitError('Field "camera" expects "<field> <value>" pairs', line, tokens[0]?.column ?? 1);
4607
+ }
4608
+ const camera = current ? { ...current } : createEmptyViewCamera2();
4609
+ const seen = /* @__PURE__ */ new Set();
4610
+ for (let index = 0; index < tokens.length; index += 2) {
4611
+ const fieldToken = tokens[index];
4612
+ const valueToken = tokens[index + 1];
4613
+ const key = fieldToken.value.toLowerCase();
4614
+ if (seen.has(key)) {
4615
+ throw new WorldOrbitError(`Duplicate viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
4616
+ }
4617
+ seen.add(key);
4618
+ const value = valueToken.value;
4619
+ switch (key) {
4620
+ case "azimuth":
4621
+ camera.azimuth = parseFiniteNumber2(value, line, fieldToken.column, "camera.azimuth");
4622
+ break;
4623
+ case "elevation":
4624
+ camera.elevation = parseFiniteNumber2(value, line, fieldToken.column, "camera.elevation");
4625
+ break;
4626
+ case "roll":
4627
+ camera.roll = parseFiniteNumber2(value, line, fieldToken.column, "camera.roll");
4628
+ break;
4629
+ case "distance":
4630
+ camera.distance = parsePositiveNumber2(value, line, fieldToken.column, "camera.distance");
4631
+ break;
4632
+ default:
4633
+ throw new WorldOrbitError(`Unknown viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
4634
+ }
4635
+ }
4636
+ return camera;
4637
+ }
4259
4638
  function parseInlineObjectFields(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
4260
4639
  const fields = [];
4261
4640
  let index = 0;
@@ -4388,7 +4767,7 @@
4388
4767
  object.tolerances = tolerances;
4389
4768
  if (typedBlocks && Object.keys(typedBlocks).length > 0)
4390
4769
  object.typedBlocks = typedBlocks;
4391
- if (sourceSchemaVersion !== "2.1") {
4770
+ if (isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
4392
4771
  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) {
4393
4772
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
4394
4773
  }
@@ -4404,23 +4783,25 @@
4404
4783
  };
4405
4784
  }
4406
4785
  function normalizeDraftEventPose(rawPose) {
4407
- const fieldMap = collectDraftFields(rawPose.fields);
4786
+ const fieldMap = collectDraftFields(rawPose.fields, "event-pose");
4408
4787
  const placement = extractPlacementFromFieldMap(fieldMap);
4409
4788
  return {
4410
4789
  objectId: rawPose.objectId,
4411
4790
  placement,
4412
4791
  inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
4413
- outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer")
4792
+ outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer"),
4793
+ epoch: parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]),
4794
+ referencePlane: parseOptionalJoinedValue(fieldMap.get("referencePlane")?.[0])
4414
4795
  };
4415
4796
  }
4416
- function collectDraftFields(fields) {
4797
+ function collectDraftFields(fields, _mode = "object") {
4417
4798
  const grouped = /* @__PURE__ */ new Map();
4418
4799
  for (const field of fields) {
4419
4800
  const spec = getDraftObjectFieldSpec(field.key);
4420
- if (!spec) {
4801
+ if (!spec && !EVENT_POSE_FIELD_KEYS.has(field.key)) {
4421
4802
  throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
4422
4803
  }
4423
- if (!spec.allowRepeat && grouped.has(field.key)) {
4804
+ if (!spec?.allowRepeat && grouped.has(field.key)) {
4424
4805
  throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
4425
4806
  }
4426
4807
  const existing = grouped.get(field.key) ?? [];
@@ -4597,7 +4978,7 @@
4597
4978
  }
4598
4979
  }
4599
4980
  function warnIfSchema21Feature(sourceSchemaVersion, diagnostics, featureName, location) {
4600
- if (sourceSchemaVersion === "2.1") {
4981
+ if (!isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
4601
4982
  return;
4602
4983
  }
4603
4984
  diagnostics.push({
@@ -4609,6 +4990,34 @@
4609
4990
  column: location.column
4610
4991
  });
4611
4992
  }
4993
+ function warnIfSchema25Feature(sourceSchemaVersion, diagnostics, featureName, location) {
4994
+ if (!isSchemaOlderThan(sourceSchemaVersion, "2.5")) {
4995
+ return;
4996
+ }
4997
+ diagnostics.push({
4998
+ code: "parse.schema25.featureCompatibility",
4999
+ severity: "warning",
5000
+ source: "parse",
5001
+ message: `Feature "${featureName}" requires schema 2.5; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
5002
+ line: location.line,
5003
+ column: location.column
5004
+ });
5005
+ }
5006
+ function isSchemaOlderThan(sourceSchemaVersion, requiredVersion) {
5007
+ return schemaVersionRank(sourceSchemaVersion) < schemaVersionRank(requiredVersion);
5008
+ }
5009
+ function schemaVersionRank(version) {
5010
+ switch (version) {
5011
+ case "2.0-draft":
5012
+ return 0;
5013
+ case "2.0":
5014
+ return 1;
5015
+ case "2.1":
5016
+ return 2;
5017
+ case "2.5":
5018
+ return 3;
5019
+ }
5020
+ }
4612
5021
  function preprocessAtlasSource(source) {
4613
5022
  const chars = [...source];
4614
5023
  const comments = [];
@@ -4696,8 +5105,9 @@
4696
5105
  }
4697
5106
 
4698
5107
  // packages/core/dist/load.js
4699
- var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1)?$/i;
5108
+ var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1|\.5)?$/i;
4700
5109
  var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
5110
+ var ATLAS_SCHEMA_25_PATTERN = /^schema\s+2\.5$/i;
4701
5111
  var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
4702
5112
  function detectWorldOrbitSchemaVersion(source) {
4703
5113
  for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
@@ -4711,6 +5121,9 @@
4711
5121
  if (ATLAS_SCHEMA_21_PATTERN.test(trimmed)) {
4712
5122
  return "2.1";
4713
5123
  }
5124
+ if (ATLAS_SCHEMA_25_PATTERN.test(trimmed)) {
5125
+ return "2.5";
5126
+ }
4714
5127
  if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
4715
5128
  return "2.0";
4716
5129
  }
@@ -4771,7 +5184,7 @@
4771
5184
  }
4772
5185
  function loadWorldOrbitSourceWithDiagnostics(source) {
4773
5186
  const schemaVersion = detectWorldOrbitSchemaVersion(source);
4774
- if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1") {
5187
+ if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5") {
4775
5188
  return loadAtlasSourceWithDiagnostics(source, schemaVersion);
4776
5189
  }
4777
5190
  let ast;