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.
@@ -948,7 +948,9 @@ var WorldOrbit = (() => {
948
948
  const height = frame.height;
949
949
  const padding = frame.padding;
950
950
  const layoutPreset = resolveLayoutPreset(document2);
951
- const projection = resolveProjection(document2, options.projection);
951
+ const schemaProjection = resolveProjection(document2, options.projection);
952
+ const camera = normalizeViewCamera(options.camera ?? null);
953
+ const renderProjection = resolveRenderProjection(schemaProjection, camera);
952
954
  const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
953
955
  const spacingFactor = layoutPresetSpacing(layoutPreset);
954
956
  const systemId = document2.system?.id ?? null;
@@ -991,7 +993,7 @@ var WorldOrbit = (() => {
991
993
  surfaceChildren,
992
994
  objectMap,
993
995
  spacingFactor,
994
- projection,
996
+ projection: renderProjection,
995
997
  scaleModel
996
998
  };
997
999
  const primaryRoot = rootObjects.find((object) => object.type === "star") ?? rootObjects[0] ?? null;
@@ -1003,7 +1005,7 @@ var WorldOrbit = (() => {
1003
1005
  const rootRingRadius = Math.min(width, height) * 0.28 * spacingFactor * scaleModel.orbitDistanceMultiplier;
1004
1006
  secondaryRoots.forEach((object, index) => {
1005
1007
  const angle = angleForIndex(index, secondaryRoots.length, -Math.PI / 2);
1006
- const offset = projectPolarOffset(angle, rootRingRadius, projection, 1);
1008
+ const offset = projectPolarOffset(angle, rootRingRadius, renderProjection, 1);
1007
1009
  placeObject(object, centerX + offset.x, centerY + offset.y, 0, positions, orbitDrafts, leaderDrafts, context);
1008
1010
  });
1009
1011
  }
@@ -1065,27 +1067,34 @@ var WorldOrbit = (() => {
1065
1067
  const layers = createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels);
1066
1068
  const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, scaleModel.labelMultiplier);
1067
1069
  const semanticGroups = createSceneSemanticGroups(document2, objects);
1068
- const viewpoints = createSceneViewpoints(document2, projection, frame.preset, relationships, objectMap);
1070
+ const viewpoints = createSceneViewpoints(document2, schemaProjection, frame.preset, relationships, objectMap);
1069
1071
  const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, scaleModel.labelMultiplier);
1070
1072
  return {
1071
1073
  width,
1072
1074
  height,
1073
1075
  padding,
1074
1076
  renderPreset: frame.preset,
1075
- projection,
1077
+ projection: schemaProjection,
1078
+ renderProjection,
1079
+ camera,
1076
1080
  scaleModel,
1077
1081
  title: String(document2.system?.title ?? document2.system?.properties.title ?? document2.system?.id ?? "WorldOrbit") || "WorldOrbit",
1078
- subtitle: `${capitalizeLabel(projection)} view - ${capitalizeLabel(layoutPreset)} layout`,
1082
+ subtitle: buildSceneSubtitle(schemaProjection, renderProjection, layoutPreset, camera),
1079
1083
  systemId,
1080
- viewMode: projection,
1084
+ viewMode: schemaProjection,
1081
1085
  layoutPreset,
1082
1086
  metadata: {
1083
1087
  format: document2.format,
1084
1088
  version: document2.version,
1085
- view: projection,
1089
+ view: schemaProjection,
1090
+ renderProjection,
1086
1091
  scale: String(document2.system?.properties.scale ?? layoutPreset),
1087
1092
  units: String(document2.system?.properties.units ?? "mixed"),
1088
- preset: frame.preset ?? "custom"
1093
+ preset: frame.preset ?? "custom",
1094
+ ...camera?.azimuth !== null ? { "camera.azimuth": String(camera?.azimuth) } : {},
1095
+ ...camera?.elevation !== null ? { "camera.elevation": String(camera?.elevation) } : {},
1096
+ ...camera?.roll !== null ? { "camera.roll": String(camera?.roll) } : {},
1097
+ ...camera?.distance !== null ? { "camera.distance": String(camera?.distance) } : {}
1089
1098
  },
1090
1099
  contentBounds,
1091
1100
  layers,
@@ -1111,21 +1120,42 @@ var WorldOrbit = (() => {
1111
1120
  return cloned;
1112
1121
  }
1113
1122
  const objectMap = new Map(cloned.map((object) => [object.id, object]));
1123
+ const referencedIds = /* @__PURE__ */ new Set([
1124
+ ...activeEvent.targetObjectId ? [activeEvent.targetObjectId] : [],
1125
+ ...activeEvent.participantObjectIds,
1126
+ ...activeEvent.positions.map((pose) => pose.objectId)
1127
+ ]);
1128
+ for (const objectId of referencedIds) {
1129
+ const object = objectMap.get(objectId);
1130
+ if (!object) {
1131
+ continue;
1132
+ }
1133
+ if (activeEvent.epoch) {
1134
+ object.epoch = activeEvent.epoch;
1135
+ }
1136
+ if (activeEvent.referencePlane) {
1137
+ object.referencePlane = activeEvent.referencePlane;
1138
+ }
1139
+ }
1114
1140
  for (const pose of activeEvent.positions) {
1115
1141
  const object = objectMap.get(pose.objectId);
1116
1142
  if (!object) {
1117
1143
  continue;
1118
1144
  }
1119
- object.placement = pose.placement ? structuredClone(pose.placement) : null;
1145
+ if (pose.placement) {
1146
+ object.placement = structuredClone(pose.placement);
1147
+ }
1120
1148
  if (pose.inner) {
1121
1149
  object.properties.inner = { ...pose.inner };
1122
- } else {
1123
- delete object.properties.inner;
1124
1150
  }
1125
1151
  if (pose.outer) {
1126
1152
  object.properties.outer = { ...pose.outer };
1127
- } else {
1128
- delete object.properties.outer;
1153
+ }
1154
+ if (pose.epoch) {
1155
+ object.epoch = pose.epoch;
1156
+ }
1157
+ if (pose.referencePlane) {
1158
+ object.referencePlane = pose.referencePlane;
1129
1159
  }
1130
1160
  }
1131
1161
  return cloned;
@@ -1166,10 +1196,59 @@ var WorldOrbit = (() => {
1166
1196
  }
1167
1197
  }
1168
1198
  function resolveProjection(document2, projection) {
1169
- if (projection === "topdown" || projection === "isometric") {
1199
+ if (projection === "topdown" || projection === "isometric" || projection === "orthographic" || projection === "perspective") {
1170
1200
  return projection;
1171
1201
  }
1172
- return String(document2.system?.properties.view ?? "topdown").toLowerCase() === "isometric" ? "isometric" : "topdown";
1202
+ const documentView = String(document2.system?.properties.view ?? "topdown").toLowerCase();
1203
+ return parseViewProjection(documentView) ?? "topdown";
1204
+ }
1205
+ function resolveRenderProjection(projection, camera) {
1206
+ switch (projection) {
1207
+ case "topdown":
1208
+ return "topdown";
1209
+ case "isometric":
1210
+ return "isometric";
1211
+ case "orthographic":
1212
+ return camera && (camera.azimuth !== null || camera.elevation !== null || camera.roll !== null) ? "isometric" : "topdown";
1213
+ case "perspective":
1214
+ return "isometric";
1215
+ }
1216
+ }
1217
+ function normalizeViewCamera(camera) {
1218
+ if (!camera) {
1219
+ return null;
1220
+ }
1221
+ const normalized = {
1222
+ azimuth: normalizeFiniteCameraValue(camera.azimuth),
1223
+ elevation: normalizeFiniteCameraValue(camera.elevation),
1224
+ roll: normalizeFiniteCameraValue(camera.roll),
1225
+ distance: normalizePositiveCameraDistance(camera.distance)
1226
+ };
1227
+ return normalized.azimuth !== null || normalized.elevation !== null || normalized.roll !== null || normalized.distance !== null ? normalized : null;
1228
+ }
1229
+ function normalizeFiniteCameraValue(value) {
1230
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1231
+ }
1232
+ function normalizePositiveCameraDistance(value) {
1233
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
1234
+ }
1235
+ function buildSceneSubtitle(projection, renderProjection, layoutPreset, camera) {
1236
+ const parts = [`${capitalizeLabel(projection)} view`, `${capitalizeLabel(layoutPreset)} layout`];
1237
+ if (projection !== renderProjection) {
1238
+ parts.push(`2D ${renderProjection} fallback`);
1239
+ }
1240
+ if (camera) {
1241
+ const cameraParts = [
1242
+ camera.azimuth !== null ? `az ${camera.azimuth}` : null,
1243
+ camera.elevation !== null ? `el ${camera.elevation}` : null,
1244
+ camera.roll !== null ? `roll ${camera.roll}` : null,
1245
+ camera.distance !== null ? `dist ${camera.distance}` : null
1246
+ ].filter(Boolean);
1247
+ if (cameraParts.length > 0) {
1248
+ parts.push(`camera ${cameraParts.join(" / ")}`);
1249
+ }
1250
+ }
1251
+ return parts.join(" - ");
1173
1252
  }
1174
1253
  function resolveScaleModel(layoutPreset, overrides) {
1175
1254
  const defaults = defaultScaleModel(layoutPreset);
@@ -1605,6 +1684,8 @@ var WorldOrbit = (() => {
1605
1684
  function createGeneratedOverviewViewpoint(document2, projection, preset) {
1606
1685
  const title = document2.system?.title ?? document2.system?.properties.title;
1607
1686
  const label = title ? `${String(title)} Overview` : "Overview";
1687
+ const camera = normalizeViewCamera(null);
1688
+ const renderProjection = resolveRenderProjection(projection, camera);
1608
1689
  return {
1609
1690
  id: "overview",
1610
1691
  label,
@@ -1613,6 +1694,8 @@ var WorldOrbit = (() => {
1613
1694
  selectedObjectId: null,
1614
1695
  eventIds: [],
1615
1696
  projection,
1697
+ renderProjection,
1698
+ camera,
1616
1699
  preset,
1617
1700
  rotationDeg: 0,
1618
1701
  scale: null,
@@ -1662,6 +1745,30 @@ var WorldOrbit = (() => {
1662
1745
  case "angle":
1663
1746
  draft.rotationDeg = parseFiniteNumber(normalizedValue) ?? draft.rotationDeg ?? 0;
1664
1747
  return;
1748
+ case "camera.azimuth":
1749
+ draft.camera = {
1750
+ ...draft.camera ?? createEmptyViewCamera(),
1751
+ azimuth: parseFiniteNumber(normalizedValue)
1752
+ };
1753
+ return;
1754
+ case "camera.elevation":
1755
+ draft.camera = {
1756
+ ...draft.camera ?? createEmptyViewCamera(),
1757
+ elevation: parseFiniteNumber(normalizedValue)
1758
+ };
1759
+ return;
1760
+ case "camera.roll":
1761
+ draft.camera = {
1762
+ ...draft.camera ?? createEmptyViewCamera(),
1763
+ roll: parseFiniteNumber(normalizedValue)
1764
+ };
1765
+ return;
1766
+ case "camera.distance":
1767
+ draft.camera = {
1768
+ ...draft.camera ?? createEmptyViewCamera(),
1769
+ distance: parsePositiveNumber(normalizedValue)
1770
+ };
1771
+ return;
1665
1772
  case "zoom":
1666
1773
  case "scale":
1667
1774
  draft.scale = parsePositiveNumber(normalizedValue);
@@ -1701,6 +1808,9 @@ var WorldOrbit = (() => {
1701
1808
  const selectedObjectId = draft.select && objectMap.has(draft.select) ? draft.select : objectId;
1702
1809
  const filter = normalizeViewpointFilter(draft.filter);
1703
1810
  const label = draft.label?.trim() || humanizeIdentifier(draft.id);
1811
+ const resolvedProjection = draft.projection ?? projection;
1812
+ const camera = normalizeViewCamera(draft.camera ?? null);
1813
+ const renderProjection = resolveRenderProjection(resolvedProjection, camera);
1704
1814
  return {
1705
1815
  id: draft.id,
1706
1816
  label,
@@ -1708,7 +1818,9 @@ var WorldOrbit = (() => {
1708
1818
  objectId,
1709
1819
  selectedObjectId,
1710
1820
  eventIds: [...new Set(draft.eventIds ?? [])],
1711
- projection: draft.projection ?? projection,
1821
+ projection: resolvedProjection,
1822
+ renderProjection,
1823
+ camera,
1712
1824
  preset: draft.preset ?? preset,
1713
1825
  rotationDeg: draft.rotationDeg ?? 0,
1714
1826
  scale: draft.scale ?? null,
@@ -1725,6 +1837,14 @@ var WorldOrbit = (() => {
1725
1837
  groupIds: []
1726
1838
  };
1727
1839
  }
1840
+ function createEmptyViewCamera() {
1841
+ return {
1842
+ azimuth: null,
1843
+ elevation: null,
1844
+ roll: null,
1845
+ distance: null
1846
+ };
1847
+ }
1728
1848
  function normalizeViewpointFilter(filter) {
1729
1849
  if (!filter) {
1730
1850
  return null;
@@ -1738,7 +1858,18 @@ var WorldOrbit = (() => {
1738
1858
  return normalized.query || normalized.objectTypes.length > 0 || normalized.tags.length > 0 || normalized.groupIds.length > 0 ? normalized : null;
1739
1859
  }
1740
1860
  function parseViewProjection(value) {
1741
- return value.toLowerCase() === "isometric" ? "isometric" : value.toLowerCase() === "topdown" ? "topdown" : null;
1861
+ switch (value.toLowerCase()) {
1862
+ case "topdown":
1863
+ return "topdown";
1864
+ case "isometric":
1865
+ return "isometric";
1866
+ case "orthographic":
1867
+ return "orthographic";
1868
+ case "perspective":
1869
+ return "perspective";
1870
+ default:
1871
+ return null;
1872
+ }
1742
1873
  }
1743
1874
  function parseRenderPreset(value) {
1744
1875
  const normalized = value.toLowerCase();
@@ -1776,7 +1907,7 @@ var WorldOrbit = (() => {
1776
1907
  }
1777
1908
  function parseViewpointGroups(value, document2, relationships, objectMap) {
1778
1909
  return splitListValue(value).map((entry) => {
1779
- if (document2.schemaVersion === "2.1" || document2.groups.some((group) => group.id === entry)) {
1910
+ if (document2.schemaVersion === "2.1" || document2.schemaVersion === "2.5" || document2.groups.some((group) => group.id === entry)) {
1780
1911
  return entry;
1781
1912
  }
1782
1913
  if (entry.startsWith("wo-") && entry.endsWith("-group")) {
@@ -2608,7 +2739,9 @@ var WorldOrbit = (() => {
2608
2739
  objectId: pose.objectId,
2609
2740
  placement: clonePlacement(pose.placement),
2610
2741
  inner: pose.inner ? { ...pose.inner } : void 0,
2611
- outer: pose.outer ? { ...pose.outer } : void 0
2742
+ outer: pose.outer ? { ...pose.outer } : void 0,
2743
+ epoch: pose.epoch ?? null,
2744
+ referencePlane: pose.referencePlane ?? null
2612
2745
  };
2613
2746
  }
2614
2747
  function clonePlacement(placement) {
@@ -2623,21 +2756,42 @@ var WorldOrbit = (() => {
2623
2756
  return;
2624
2757
  }
2625
2758
  const objectMap = new Map(objects.map((object) => [object.id, object]));
2759
+ const referencedIds = /* @__PURE__ */ new Set([
2760
+ ...event.targetObjectId ? [event.targetObjectId] : [],
2761
+ ...event.participantObjectIds,
2762
+ ...event.positions.map((pose) => pose.objectId)
2763
+ ]);
2764
+ for (const objectId of referencedIds) {
2765
+ const object = objectMap.get(objectId);
2766
+ if (!object) {
2767
+ continue;
2768
+ }
2769
+ if (event.epoch) {
2770
+ object.epoch = event.epoch;
2771
+ }
2772
+ if (event.referencePlane) {
2773
+ object.referencePlane = event.referencePlane;
2774
+ }
2775
+ }
2626
2776
  for (const pose of event.positions) {
2627
2777
  const object = objectMap.get(pose.objectId);
2628
2778
  if (!object) {
2629
2779
  continue;
2630
2780
  }
2631
- object.placement = clonePlacement(pose.placement);
2781
+ if (pose.placement) {
2782
+ object.placement = clonePlacement(pose.placement);
2783
+ }
2632
2784
  if (pose.inner) {
2633
2785
  object.properties.inner = { ...pose.inner };
2634
- } else {
2635
- delete object.properties.inner;
2636
2786
  }
2637
2787
  if (pose.outer) {
2638
2788
  object.properties.outer = { ...pose.outer };
2639
- } else {
2640
- delete object.properties.outer;
2789
+ }
2790
+ if (pose.epoch) {
2791
+ object.epoch = pose.epoch;
2792
+ }
2793
+ if (pose.referencePlane) {
2794
+ object.referencePlane = pose.referencePlane;
2641
2795
  }
2642
2796
  }
2643
2797
  }
@@ -2713,6 +2867,18 @@ var WorldOrbit = (() => {
2713
2867
  if (viewpoint.rotationDeg !== 0) {
2714
2868
  info2[`${prefix}.rotation`] = String(viewpoint.rotationDeg);
2715
2869
  }
2870
+ if (viewpoint.camera?.azimuth !== null) {
2871
+ info2[`${prefix}.camera.azimuth`] = String(viewpoint.camera?.azimuth);
2872
+ }
2873
+ if (viewpoint.camera?.elevation !== null) {
2874
+ info2[`${prefix}.camera.elevation`] = String(viewpoint.camera?.elevation);
2875
+ }
2876
+ if (viewpoint.camera?.roll !== null) {
2877
+ info2[`${prefix}.camera.roll`] = String(viewpoint.camera?.roll);
2878
+ }
2879
+ if (viewpoint.camera?.distance !== null) {
2880
+ info2[`${prefix}.camera.distance`] = String(viewpoint.camera?.distance);
2881
+ }
2716
2882
  const serializedLayers = serializeViewpointLayers(viewpoint.layers);
2717
2883
  if (serializedLayers) {
2718
2884
  info2[`${prefix}.layers`] = serializedLayers;
@@ -2956,13 +3122,13 @@ var WorldOrbit = (() => {
2956
3122
  validateRelation(relation, objectMap, diagnostics);
2957
3123
  }
2958
3124
  for (const viewpoint of document2.system?.viewpoints ?? []) {
2959
- validateViewpoint(viewpoint.filter, viewpoint.events ?? [], groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpoint.id);
3125
+ validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap);
2960
3126
  }
2961
3127
  for (const object of document2.objects) {
2962
3128
  validateObject(object, document2.system, objectMap, groupIds, diagnostics);
2963
3129
  }
2964
3130
  for (const event of document2.events) {
2965
- validateEvent(event, objectMap, diagnostics);
3131
+ validateEvent(event, document2.system, objectMap, diagnostics);
2966
3132
  }
2967
3133
  return diagnostics;
2968
3134
  }
@@ -2981,21 +3147,24 @@ var WorldOrbit = (() => {
2981
3147
  diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
2982
3148
  }
2983
3149
  }
2984
- function validateViewpoint(filter, eventRefs, groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpointId) {
2985
- if (sourceSchemaVersion === "2.1") {
3150
+ function validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap) {
3151
+ const filter = viewpoint.filter;
3152
+ if (sourceSchemaVersion === "2.1" || sourceSchemaVersion === "2.5") {
2986
3153
  if (filter) {
2987
3154
  for (const groupId of filter.groupIds) {
2988
3155
  if (!groupIds.has(groupId)) {
2989
- diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.groups`));
3156
+ diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.groups`));
2990
3157
  }
2991
3158
  }
2992
3159
  }
2993
- for (const eventId of eventRefs) {
3160
+ for (const eventId of viewpoint.events ?? []) {
2994
3161
  if (!eventIds.has(eventId)) {
2995
- diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.events`));
3162
+ diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.events`));
2996
3163
  }
2997
3164
  }
2998
3165
  }
3166
+ validateProjection(viewpoint.projection, diagnostics, `viewpoint.${viewpoint.id}.projection`, viewpoint.id);
3167
+ validateCamera(viewpoint.camera, viewpoint.projection, viewpoint.rotationDeg, diagnostics, viewpoint.id, viewpoint.focusObjectId, viewpoint.selectedObjectId, filter, objectMap);
2999
3168
  }
3000
3169
  function validateObject(object, system, objectMap, groupIds, diagnostics) {
3001
3170
  const placement = object.placement;
@@ -3008,6 +3177,12 @@ var WorldOrbit = (() => {
3008
3177
  }
3009
3178
  }
3010
3179
  }
3180
+ if (typeof object.epoch === "string" && !object.epoch.trim()) {
3181
+ diagnostics.push(warn("validate.epoch.empty", `Object "${object.id}" defines an empty epoch string.`, object.id, "epoch"));
3182
+ }
3183
+ if (typeof object.referencePlane === "string" && !object.referencePlane.trim()) {
3184
+ diagnostics.push(warn("validate.referencePlane.empty", `Object "${object.id}" defines an empty reference plane string.`, object.id, "referencePlane"));
3185
+ }
3011
3186
  if (orbitPlacement) {
3012
3187
  if (!objectMap.has(orbitPlacement.target)) {
3013
3188
  diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
@@ -3079,12 +3254,18 @@ var WorldOrbit = (() => {
3079
3254
  }
3080
3255
  }
3081
3256
  }
3082
- function validateEvent(event, objectMap, diagnostics) {
3257
+ function validateEvent(event, system, objectMap, diagnostics) {
3083
3258
  const fieldPrefix = `event.${event.id}`;
3084
3259
  const referencedIds = /* @__PURE__ */ new Set();
3085
3260
  if (!event.kind.trim()) {
3086
3261
  diagnostics.push(error("validate.event.kind.required", `Event "${event.id}" is missing a "kind" value.`, void 0, `${fieldPrefix}.kind`));
3087
3262
  }
3263
+ if (typeof event.epoch === "string" && !event.epoch.trim()) {
3264
+ diagnostics.push(warn("validate.event.epoch.empty", `Event "${event.id}" defines an empty epoch string.`, void 0, `${fieldPrefix}.epoch`));
3265
+ }
3266
+ if (typeof event.referencePlane === "string" && !event.referencePlane.trim()) {
3267
+ diagnostics.push(warn("validate.event.referencePlane.empty", `Event "${event.id}" defines an empty reference plane string.`, void 0, `${fieldPrefix}.referencePlane`));
3268
+ }
3088
3269
  if (!event.targetObjectId && event.participantObjectIds.length === 0) {
3089
3270
  diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
3090
3271
  }
@@ -3131,10 +3312,14 @@ var WorldOrbit = (() => {
3131
3312
  if (!referencedIds.has(pose.objectId)) {
3132
3313
  diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
3133
3314
  }
3134
- validateEventPose(pose, object, objectMap, diagnostics, poseFieldPrefix, event.id);
3315
+ validateEventPose(pose, object, event, system, objectMap, diagnostics, poseFieldPrefix, event.id);
3316
+ }
3317
+ const missingPoseIds = [...referencedIds].filter((objectId) => !poseIds.has(objectId));
3318
+ if (event.positions.length > 0 && missingPoseIds.length > 0) {
3319
+ diagnostics.push(warn("validate.event.positions.partial", `Event "${event.id}" leaves ${missingPoseIds.length} referenced object(s) on their base placement.`, void 0, `${fieldPrefix}.positions`));
3135
3320
  }
3136
3321
  }
3137
- function validateEventPose(pose, object, objectMap, diagnostics, fieldPrefix, eventId) {
3322
+ function validateEventPose(pose, object, event, system, objectMap, diagnostics, fieldPrefix, eventId) {
3138
3323
  const placement = pose.placement;
3139
3324
  if (!placement) {
3140
3325
  diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
@@ -3147,6 +3332,15 @@ var WorldOrbit = (() => {
3147
3332
  if (placement.distance && placement.semiMajor) {
3148
3333
  diagnostics.push(error("validate.event.pose.orbit.distanceConflict", `Event "${eventId}" pose "${pose.objectId}" cannot declare both "distance" and "semiMajor".`, void 0, `${fieldPrefix}.distance`));
3149
3334
  }
3335
+ if (placement.phase && !resolveEffectiveEpoch(system, object, event, pose)) {
3336
+ diagnostics.push(warn("validate.event.pose.phase.epochMissing", `Event "${eventId}" pose "${pose.objectId}" sets "phase" without an effective epoch.`, void 0, `${fieldPrefix}.phase`));
3337
+ }
3338
+ if (placement.inclination && !resolveEffectiveReferencePlane(system, object, event, pose)) {
3339
+ diagnostics.push(warn("validate.event.pose.inclination.referencePlaneMissing", `Event "${eventId}" pose "${pose.objectId}" sets "inclination" without an effective reference plane.`, void 0, `${fieldPrefix}.inclination`));
3340
+ }
3341
+ if (placement.period && !massInSolar(objectMap.get(placement.target)?.properties.mass)) {
3342
+ 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`));
3343
+ }
3150
3344
  return;
3151
3345
  }
3152
3346
  if (placement.mode === "surface") {
@@ -3281,6 +3475,52 @@ var WorldOrbit = (() => {
3281
3475
  return null;
3282
3476
  }
3283
3477
  }
3478
+ function validateProjection(projection, diagnostics, field, viewpointId) {
3479
+ if (projection !== "topdown" && projection !== "isometric" && projection !== "orthographic" && projection !== "perspective") {
3480
+ diagnostics.push(error("validate.viewpoint.projection.invalid", `Unknown projection "${String(projection)}" in viewpoint "${viewpointId}".`, void 0, field));
3481
+ }
3482
+ }
3483
+ function validateCamera(camera, projection, rotationDeg, diagnostics, viewpointId, focusObjectId, selectedObjectId, filter, objectMap) {
3484
+ if (!camera) {
3485
+ return;
3486
+ }
3487
+ const prefix = `viewpoint.${viewpointId}.camera`;
3488
+ for (const [key, value] of [
3489
+ ["azimuth", camera.azimuth],
3490
+ ["elevation", camera.elevation],
3491
+ ["roll", camera.roll],
3492
+ ["distance", camera.distance]
3493
+ ]) {
3494
+ if (value !== null && (!Number.isFinite(value) || key === "distance" && value <= 0)) {
3495
+ diagnostics.push(error("validate.viewpoint.camera.invalid", `Invalid camera ${key} "${String(value)}" in viewpoint "${viewpointId}".`, void 0, `${prefix}.${key}`));
3496
+ }
3497
+ }
3498
+ if (camera.distance !== null && projection !== "perspective") {
3499
+ 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`));
3500
+ }
3501
+ if (projection === "topdown" && (camera.elevation !== null || camera.roll !== null)) {
3502
+ 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));
3503
+ }
3504
+ if (projection === "isometric" && camera.elevation !== null) {
3505
+ diagnostics.push(info("validate.viewpoint.camera.isometricStored", `Camera elevation on isometric viewpoint "${viewpointId}" is preserved semantically for future 3D rendering.`, void 0, `${prefix}.elevation`));
3506
+ }
3507
+ if (camera.azimuth !== null && camera.azimuth !== 0 && rotationDeg !== 0) {
3508
+ 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`));
3509
+ }
3510
+ const hasAnchor = focusObjectId !== null && objectMap.has(focusObjectId) || selectedObjectId !== null && objectMap.has(selectedObjectId) || !!filter;
3511
+ if (!hasAnchor) {
3512
+ diagnostics.push(info("validate.viewpoint.camera.anchorMissing", `Viewpoint "${viewpointId}" stores camera settings without a focus object, selection, or filter anchor.`, void 0, prefix));
3513
+ }
3514
+ }
3515
+ function resolveEffectiveEpoch(system, object, event, pose) {
3516
+ return normalizeOptionalContextString(pose?.epoch) ?? normalizeOptionalContextString(event?.epoch) ?? normalizeOptionalContextString(object.epoch) ?? normalizeOptionalContextString(system?.epoch) ?? null;
3517
+ }
3518
+ function resolveEffectiveReferencePlane(system, object, event, pose) {
3519
+ return normalizeOptionalContextString(pose?.referencePlane) ?? normalizeOptionalContextString(event?.referencePlane) ?? normalizeOptionalContextString(object.referencePlane) ?? normalizeOptionalContextString(system?.referencePlane) ?? null;
3520
+ }
3521
+ function normalizeOptionalContextString(value) {
3522
+ return typeof value === "string" && value.trim() ? value.trim() : null;
3523
+ }
3284
3524
  function toleranceForField(object, field) {
3285
3525
  const tolerance = object.tolerances?.find((entry) => entry.field === field)?.value;
3286
3526
  if (typeof tolerance === "number") {
@@ -3389,7 +3629,9 @@ var WorldOrbit = (() => {
3389
3629
  "surface",
3390
3630
  "free",
3391
3631
  "inner",
3392
- "outer"
3632
+ "outer",
3633
+ "epoch",
3634
+ "referencePlane"
3393
3635
  ]);
3394
3636
  function parseWorldOrbitAtlas(source) {
3395
3637
  return parseAtlasSource(source);
@@ -3431,7 +3673,7 @@ var WorldOrbit = (() => {
3431
3673
  if (!sawSchemaHeader) {
3432
3674
  sourceSchemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
3433
3675
  sawSchemaHeader = true;
3434
- if (prepared.comments.length > 0 && sourceSchemaVersion !== "2.1") {
3676
+ if (prepared.comments.length > 0 && isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
3435
3677
  diagnostics.push({
3436
3678
  code: "parse.schema21.commentCompatibility",
3437
3679
  severity: "warning",
@@ -3501,11 +3743,11 @@ var WorldOrbit = (() => {
3501
3743
  return document2;
3502
3744
  }
3503
3745
  function assertDraftSchemaHeader(tokens, line) {
3504
- if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1"].includes(tokens[1].value.toLowerCase())) {
3505
- throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
3746
+ if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1", "2.5"].includes(tokens[1].value.toLowerCase())) {
3747
+ 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);
3506
3748
  }
3507
3749
  const version = tokens[1].value.toLowerCase();
3508
- return version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
3750
+ return version === "2.5" ? "2.5" : version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
3509
3751
  }
3510
3752
  function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
3511
3753
  const keyword = tokens[0]?.value.toLowerCase();
@@ -3525,6 +3767,8 @@ var WorldOrbit = (() => {
3525
3767
  return {
3526
3768
  kind: "defaults",
3527
3769
  system,
3770
+ sourceSchemaVersion,
3771
+ diagnostics,
3528
3772
  seenFields: /* @__PURE__ */ new Set()
3529
3773
  };
3530
3774
  case "atlas":
@@ -3617,6 +3861,7 @@ var WorldOrbit = (() => {
3617
3861
  preset: system.defaults.preset,
3618
3862
  zoom: null,
3619
3863
  rotationDeg: 0,
3864
+ camera: null,
3620
3865
  layers: {},
3621
3866
  filter: null
3622
3867
  };
@@ -3630,7 +3875,10 @@ var WorldOrbit = (() => {
3630
3875
  seenFields: /* @__PURE__ */ new Set(),
3631
3876
  inFilter: false,
3632
3877
  filterIndent: null,
3633
- seenFilterFields: /* @__PURE__ */ new Set()
3878
+ seenFilterFields: /* @__PURE__ */ new Set(),
3879
+ inCamera: false,
3880
+ cameraIndent: null,
3881
+ seenCameraFields: /* @__PURE__ */ new Set()
3634
3882
  };
3635
3883
  }
3636
3884
  function startAnnotationSection(tokens, line, system, annotationIds) {
@@ -3737,6 +3985,8 @@ var WorldOrbit = (() => {
3737
3985
  participantObjectIds: [],
3738
3986
  timing: null,
3739
3987
  visibility: null,
3988
+ epoch: null,
3989
+ referencePlane: null,
3740
3990
  tags: [],
3741
3991
  color: null,
3742
3992
  hidden: false,
@@ -3861,6 +4111,12 @@ var WorldOrbit = (() => {
3861
4111
  const value = joinFieldValue(tokens, line);
3862
4112
  switch (key) {
3863
4113
  case "view":
4114
+ if (isSchema25Projection(value)) {
4115
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "defaults.view", {
4116
+ line,
4117
+ column: tokens[0].column
4118
+ });
4119
+ }
3864
4120
  section.system.defaults.view = parseProjectionValue(value, line, tokens[0].column);
3865
4121
  return;
3866
4122
  case "scale":
@@ -3900,14 +4156,36 @@ var WorldOrbit = (() => {
3900
4156
  throw new WorldOrbitError(`Unknown atlas field "${tokens[0].value}"`, line, tokens[0].column);
3901
4157
  }
3902
4158
  function applyViewpointField2(section, indent, tokens, line) {
4159
+ if (section.inCamera && indent <= (section.cameraIndent ?? 0)) {
4160
+ section.inCamera = false;
4161
+ section.cameraIndent = null;
4162
+ }
3903
4163
  if (section.inFilter && indent <= (section.filterIndent ?? 0)) {
3904
4164
  section.inFilter = false;
3905
4165
  section.filterIndent = null;
3906
4166
  }
4167
+ if (section.inCamera) {
4168
+ applyViewpointCameraField(section, tokens, line);
4169
+ return;
4170
+ }
3907
4171
  if (section.inFilter) {
3908
4172
  applyViewpointFilterField(section, tokens, line);
3909
4173
  return;
3910
4174
  }
4175
+ if (tokens.length === 1 && tokens[0].value.toLowerCase() === "camera") {
4176
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
4177
+ line,
4178
+ column: tokens[0].column
4179
+ });
4180
+ if (section.seenFields.has("camera")) {
4181
+ throw new WorldOrbitError('Duplicate viewpoint field "camera"', line, tokens[0].column);
4182
+ }
4183
+ section.seenFields.add("camera");
4184
+ section.inCamera = true;
4185
+ section.cameraIndent = indent;
4186
+ section.viewpoint.camera = section.viewpoint.camera ?? createEmptyViewCamera2();
4187
+ return;
4188
+ }
3911
4189
  if (tokens.length === 1 && tokens[0].value.toLowerCase() === "filter") {
3912
4190
  if (section.seenFields.has("filter")) {
3913
4191
  throw new WorldOrbitError('Duplicate viewpoint field "filter"', line, tokens[0].column);
@@ -3933,6 +4211,12 @@ var WorldOrbit = (() => {
3933
4211
  section.viewpoint.selectedObjectId = value;
3934
4212
  return;
3935
4213
  case "projection":
4214
+ if (isSchema25Projection(value)) {
4215
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "projection", {
4216
+ line,
4217
+ column: tokens[0].column
4218
+ });
4219
+ }
3936
4220
  section.viewpoint.projection = parseProjectionValue(value, line, tokens[0].column);
3937
4221
  return;
3938
4222
  case "preset":
@@ -3944,6 +4228,13 @@ var WorldOrbit = (() => {
3944
4228
  case "rotation":
3945
4229
  section.viewpoint.rotationDeg = parseFiniteNumber2(value, line, tokens[0].column, "rotation");
3946
4230
  return;
4231
+ case "camera":
4232
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
4233
+ line,
4234
+ column: tokens[0].column
4235
+ });
4236
+ section.viewpoint.camera = parseInlineViewCamera(tokens.slice(1), line, section.viewpoint.camera);
4237
+ return;
3947
4238
  case "layers":
3948
4239
  section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line, section.sourceSchemaVersion, section.diagnostics);
3949
4240
  return;
@@ -3958,6 +4249,28 @@ var WorldOrbit = (() => {
3958
4249
  throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
3959
4250
  }
3960
4251
  }
4252
+ function applyViewpointCameraField(section, tokens, line) {
4253
+ const key = requireUniqueField(tokens, section.seenCameraFields, line);
4254
+ const value = joinFieldValue(tokens, line);
4255
+ const camera = section.viewpoint.camera ?? createEmptyViewCamera2();
4256
+ switch (key) {
4257
+ case "azimuth":
4258
+ camera.azimuth = parseFiniteNumber2(value, line, tokens[0].column, "camera.azimuth");
4259
+ break;
4260
+ case "elevation":
4261
+ camera.elevation = parseFiniteNumber2(value, line, tokens[0].column, "camera.elevation");
4262
+ break;
4263
+ case "roll":
4264
+ camera.roll = parseFiniteNumber2(value, line, tokens[0].column, "camera.roll");
4265
+ break;
4266
+ case "distance":
4267
+ camera.distance = parsePositiveNumber2(value, line, tokens[0].column, "camera.distance");
4268
+ break;
4269
+ default:
4270
+ throw new WorldOrbitError(`Unknown viewpoint camera field "${tokens[0].value}"`, line, tokens[0].column);
4271
+ }
4272
+ section.viewpoint.camera = camera;
4273
+ }
3961
4274
  function applyViewpointFilterField(section, tokens, line) {
3962
4275
  const key = requireUniqueField(tokens, section.seenFilterFields, line);
3963
4276
  const filter = section.viewpoint.filter ?? createEmptyViewpointFilter2();
@@ -4068,6 +4381,12 @@ var WorldOrbit = (() => {
4068
4381
  section.positionsIndent = null;
4069
4382
  }
4070
4383
  if (section.activePose) {
4384
+ if (tokens[0]?.value === "epoch" || tokens[0]?.value === "referencePlane") {
4385
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, `pose.${tokens[0].value}`, {
4386
+ line,
4387
+ column: tokens[0]?.column ?? 1
4388
+ });
4389
+ }
4071
4390
  section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
4072
4391
  return;
4073
4392
  }
@@ -4122,6 +4441,20 @@ var WorldOrbit = (() => {
4122
4441
  case "visibility":
4123
4442
  section.event.visibility = joinFieldValue(tokens, line);
4124
4443
  return;
4444
+ case "epoch":
4445
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.epoch", {
4446
+ line,
4447
+ column: tokens[0].column
4448
+ });
4449
+ section.event.epoch = joinFieldValue(tokens, line);
4450
+ return;
4451
+ case "referenceplane":
4452
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.referencePlane", {
4453
+ line,
4454
+ column: tokens[0].column
4455
+ });
4456
+ section.event.referencePlane = joinFieldValue(tokens, line);
4457
+ return;
4125
4458
  case "tags":
4126
4459
  section.event.tags = parseTokenList(tokens.slice(1), line, "tags");
4127
4460
  return;
@@ -4249,11 +4582,15 @@ var WorldOrbit = (() => {
4249
4582
  }
4250
4583
  function parseProjectionValue(value, line, column) {
4251
4584
  const normalized = value.toLowerCase();
4252
- if (normalized !== "topdown" && normalized !== "isometric") {
4585
+ if (normalized !== "topdown" && normalized !== "isometric" && normalized !== "orthographic" && normalized !== "perspective") {
4253
4586
  throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
4254
4587
  }
4255
4588
  return normalized;
4256
4589
  }
4590
+ function isSchema25Projection(value) {
4591
+ const normalized = value.toLowerCase();
4592
+ return normalized === "orthographic" || normalized === "perspective";
4593
+ }
4257
4594
  function parsePresetValue(value, line, column) {
4258
4595
  const normalized = value.toLowerCase();
4259
4596
  if (normalized === "diagram" || normalized === "presentation" || normalized === "atlas-card" || normalized === "markdown") {
@@ -4283,6 +4620,48 @@ var WorldOrbit = (() => {
4283
4620
  groupIds: []
4284
4621
  };
4285
4622
  }
4623
+ function createEmptyViewCamera2() {
4624
+ return {
4625
+ azimuth: null,
4626
+ elevation: null,
4627
+ roll: null,
4628
+ distance: null
4629
+ };
4630
+ }
4631
+ function parseInlineViewCamera(tokens, line, current) {
4632
+ if (tokens.length === 0 || tokens.length % 2 !== 0) {
4633
+ throw new WorldOrbitError('Field "camera" expects "<field> <value>" pairs', line, tokens[0]?.column ?? 1);
4634
+ }
4635
+ const camera = current ? { ...current } : createEmptyViewCamera2();
4636
+ const seen = /* @__PURE__ */ new Set();
4637
+ for (let index = 0; index < tokens.length; index += 2) {
4638
+ const fieldToken = tokens[index];
4639
+ const valueToken = tokens[index + 1];
4640
+ const key = fieldToken.value.toLowerCase();
4641
+ if (seen.has(key)) {
4642
+ throw new WorldOrbitError(`Duplicate viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
4643
+ }
4644
+ seen.add(key);
4645
+ const value = valueToken.value;
4646
+ switch (key) {
4647
+ case "azimuth":
4648
+ camera.azimuth = parseFiniteNumber2(value, line, fieldToken.column, "camera.azimuth");
4649
+ break;
4650
+ case "elevation":
4651
+ camera.elevation = parseFiniteNumber2(value, line, fieldToken.column, "camera.elevation");
4652
+ break;
4653
+ case "roll":
4654
+ camera.roll = parseFiniteNumber2(value, line, fieldToken.column, "camera.roll");
4655
+ break;
4656
+ case "distance":
4657
+ camera.distance = parsePositiveNumber2(value, line, fieldToken.column, "camera.distance");
4658
+ break;
4659
+ default:
4660
+ throw new WorldOrbitError(`Unknown viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
4661
+ }
4662
+ }
4663
+ return camera;
4664
+ }
4286
4665
  function parseInlineObjectFields(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
4287
4666
  const fields = [];
4288
4667
  let index = 0;
@@ -4415,7 +4794,7 @@ var WorldOrbit = (() => {
4415
4794
  object.tolerances = tolerances;
4416
4795
  if (typedBlocks && Object.keys(typedBlocks).length > 0)
4417
4796
  object.typedBlocks = typedBlocks;
4418
- if (sourceSchemaVersion !== "2.1") {
4797
+ if (isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
4419
4798
  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) {
4420
4799
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
4421
4800
  }
@@ -4431,23 +4810,25 @@ var WorldOrbit = (() => {
4431
4810
  };
4432
4811
  }
4433
4812
  function normalizeDraftEventPose(rawPose) {
4434
- const fieldMap = collectDraftFields(rawPose.fields);
4813
+ const fieldMap = collectDraftFields(rawPose.fields, "event-pose");
4435
4814
  const placement = extractPlacementFromFieldMap(fieldMap);
4436
4815
  return {
4437
4816
  objectId: rawPose.objectId,
4438
4817
  placement,
4439
4818
  inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
4440
- outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer")
4819
+ outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer"),
4820
+ epoch: parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]),
4821
+ referencePlane: parseOptionalJoinedValue(fieldMap.get("referencePlane")?.[0])
4441
4822
  };
4442
4823
  }
4443
- function collectDraftFields(fields) {
4824
+ function collectDraftFields(fields, _mode = "object") {
4444
4825
  const grouped = /* @__PURE__ */ new Map();
4445
4826
  for (const field of fields) {
4446
4827
  const spec = getDraftObjectFieldSpec(field.key);
4447
- if (!spec) {
4828
+ if (!spec && !EVENT_POSE_FIELD_KEYS.has(field.key)) {
4448
4829
  throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
4449
4830
  }
4450
- if (!spec.allowRepeat && grouped.has(field.key)) {
4831
+ if (!spec?.allowRepeat && grouped.has(field.key)) {
4451
4832
  throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
4452
4833
  }
4453
4834
  const existing = grouped.get(field.key) ?? [];
@@ -4624,7 +5005,7 @@ var WorldOrbit = (() => {
4624
5005
  }
4625
5006
  }
4626
5007
  function warnIfSchema21Feature(sourceSchemaVersion, diagnostics, featureName, location) {
4627
- if (sourceSchemaVersion === "2.1") {
5008
+ if (!isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
4628
5009
  return;
4629
5010
  }
4630
5011
  diagnostics.push({
@@ -4636,6 +5017,34 @@ var WorldOrbit = (() => {
4636
5017
  column: location.column
4637
5018
  });
4638
5019
  }
5020
+ function warnIfSchema25Feature(sourceSchemaVersion, diagnostics, featureName, location) {
5021
+ if (!isSchemaOlderThan(sourceSchemaVersion, "2.5")) {
5022
+ return;
5023
+ }
5024
+ diagnostics.push({
5025
+ code: "parse.schema25.featureCompatibility",
5026
+ severity: "warning",
5027
+ source: "parse",
5028
+ message: `Feature "${featureName}" requires schema 2.5; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
5029
+ line: location.line,
5030
+ column: location.column
5031
+ });
5032
+ }
5033
+ function isSchemaOlderThan(sourceSchemaVersion, requiredVersion) {
5034
+ return schemaVersionRank(sourceSchemaVersion) < schemaVersionRank(requiredVersion);
5035
+ }
5036
+ function schemaVersionRank(version) {
5037
+ switch (version) {
5038
+ case "2.0-draft":
5039
+ return 0;
5040
+ case "2.0":
5041
+ return 1;
5042
+ case "2.1":
5043
+ return 2;
5044
+ case "2.5":
5045
+ return 3;
5046
+ }
5047
+ }
4639
5048
  function preprocessAtlasSource(source) {
4640
5049
  const chars = [...source];
4641
5050
  const comments = [];
@@ -4723,8 +5132,9 @@ var WorldOrbit = (() => {
4723
5132
  }
4724
5133
 
4725
5134
  // packages/core/dist/load.js
4726
- var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1)?$/i;
5135
+ var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1|\.5)?$/i;
4727
5136
  var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
5137
+ var ATLAS_SCHEMA_25_PATTERN = /^schema\s+2\.5$/i;
4728
5138
  var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
4729
5139
  function detectWorldOrbitSchemaVersion(source) {
4730
5140
  for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
@@ -4738,6 +5148,9 @@ var WorldOrbit = (() => {
4738
5148
  if (ATLAS_SCHEMA_21_PATTERN.test(trimmed)) {
4739
5149
  return "2.1";
4740
5150
  }
5151
+ if (ATLAS_SCHEMA_25_PATTERN.test(trimmed)) {
5152
+ return "2.5";
5153
+ }
4741
5154
  if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
4742
5155
  return "2.0";
4743
5156
  }
@@ -4798,7 +5211,7 @@ var WorldOrbit = (() => {
4798
5211
  }
4799
5212
  function loadWorldOrbitSourceWithDiagnostics(source) {
4800
5213
  const schemaVersion = detectWorldOrbitSchemaVersion(source);
4801
- if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1") {
5214
+ if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5") {
4802
5215
  return loadAtlasSourceWithDiagnostics(source, schemaVersion);
4803
5216
  }
4804
5217
  let ast;