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.
@@ -1069,7 +1069,9 @@ var WorldOrbit = (() => {
1069
1069
  const height = frame.height;
1070
1070
  const padding = frame.padding;
1071
1071
  const layoutPreset = resolveLayoutPreset(document);
1072
- const projection = resolveProjection(document, options.projection);
1072
+ const schemaProjection = resolveProjection(document, options.projection);
1073
+ const camera = normalizeViewCamera(options.camera ?? null);
1074
+ const renderProjection = resolveRenderProjection(schemaProjection, camera);
1073
1075
  const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
1074
1076
  const spacingFactor = layoutPresetSpacing(layoutPreset);
1075
1077
  const systemId = document.system?.id ?? null;
@@ -1112,7 +1114,7 @@ var WorldOrbit = (() => {
1112
1114
  surfaceChildren,
1113
1115
  objectMap,
1114
1116
  spacingFactor,
1115
- projection,
1117
+ projection: renderProjection,
1116
1118
  scaleModel
1117
1119
  };
1118
1120
  const primaryRoot = rootObjects.find((object) => object.type === "star") ?? rootObjects[0] ?? null;
@@ -1124,7 +1126,7 @@ var WorldOrbit = (() => {
1124
1126
  const rootRingRadius = Math.min(width, height) * 0.28 * spacingFactor * scaleModel.orbitDistanceMultiplier;
1125
1127
  secondaryRoots.forEach((object, index) => {
1126
1128
  const angle = angleForIndex(index, secondaryRoots.length, -Math.PI / 2);
1127
- const offset = projectPolarOffset(angle, rootRingRadius, projection, 1);
1129
+ const offset = projectPolarOffset(angle, rootRingRadius, renderProjection, 1);
1128
1130
  placeObject(object, centerX + offset.x, centerY + offset.y, 0, positions, orbitDrafts, leaderDrafts, context);
1129
1131
  });
1130
1132
  }
@@ -1186,27 +1188,34 @@ var WorldOrbit = (() => {
1186
1188
  const layers = createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels);
1187
1189
  const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, scaleModel.labelMultiplier);
1188
1190
  const semanticGroups = createSceneSemanticGroups(document, objects);
1189
- const viewpoints = createSceneViewpoints(document, projection, frame.preset, relationships, objectMap);
1191
+ const viewpoints = createSceneViewpoints(document, schemaProjection, frame.preset, relationships, objectMap);
1190
1192
  const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, scaleModel.labelMultiplier);
1191
1193
  return {
1192
1194
  width,
1193
1195
  height,
1194
1196
  padding,
1195
1197
  renderPreset: frame.preset,
1196
- projection,
1198
+ projection: schemaProjection,
1199
+ renderProjection,
1200
+ camera,
1197
1201
  scaleModel,
1198
1202
  title: String(document.system?.title ?? document.system?.properties.title ?? document.system?.id ?? "WorldOrbit") || "WorldOrbit",
1199
- subtitle: `${capitalizeLabel(projection)} view - ${capitalizeLabel(layoutPreset)} layout`,
1203
+ subtitle: buildSceneSubtitle(schemaProjection, renderProjection, layoutPreset, camera),
1200
1204
  systemId,
1201
- viewMode: projection,
1205
+ viewMode: schemaProjection,
1202
1206
  layoutPreset,
1203
1207
  metadata: {
1204
1208
  format: document.format,
1205
1209
  version: document.version,
1206
- view: projection,
1210
+ view: schemaProjection,
1211
+ renderProjection,
1207
1212
  scale: String(document.system?.properties.scale ?? layoutPreset),
1208
1213
  units: String(document.system?.properties.units ?? "mixed"),
1209
- preset: frame.preset ?? "custom"
1214
+ preset: frame.preset ?? "custom",
1215
+ ...camera?.azimuth !== null ? { "camera.azimuth": String(camera?.azimuth) } : {},
1216
+ ...camera?.elevation !== null ? { "camera.elevation": String(camera?.elevation) } : {},
1217
+ ...camera?.roll !== null ? { "camera.roll": String(camera?.roll) } : {},
1218
+ ...camera?.distance !== null ? { "camera.distance": String(camera?.distance) } : {}
1210
1219
  },
1211
1220
  contentBounds,
1212
1221
  layers,
@@ -1243,21 +1252,42 @@ var WorldOrbit = (() => {
1243
1252
  return cloned;
1244
1253
  }
1245
1254
  const objectMap = new Map(cloned.map((object) => [object.id, object]));
1255
+ const referencedIds = /* @__PURE__ */ new Set([
1256
+ ...activeEvent.targetObjectId ? [activeEvent.targetObjectId] : [],
1257
+ ...activeEvent.participantObjectIds,
1258
+ ...activeEvent.positions.map((pose) => pose.objectId)
1259
+ ]);
1260
+ for (const objectId of referencedIds) {
1261
+ const object = objectMap.get(objectId);
1262
+ if (!object) {
1263
+ continue;
1264
+ }
1265
+ if (activeEvent.epoch) {
1266
+ object.epoch = activeEvent.epoch;
1267
+ }
1268
+ if (activeEvent.referencePlane) {
1269
+ object.referencePlane = activeEvent.referencePlane;
1270
+ }
1271
+ }
1246
1272
  for (const pose of activeEvent.positions) {
1247
1273
  const object = objectMap.get(pose.objectId);
1248
1274
  if (!object) {
1249
1275
  continue;
1250
1276
  }
1251
- object.placement = pose.placement ? structuredClone(pose.placement) : null;
1277
+ if (pose.placement) {
1278
+ object.placement = structuredClone(pose.placement);
1279
+ }
1252
1280
  if (pose.inner) {
1253
1281
  object.properties.inner = { ...pose.inner };
1254
- } else {
1255
- delete object.properties.inner;
1256
1282
  }
1257
1283
  if (pose.outer) {
1258
1284
  object.properties.outer = { ...pose.outer };
1259
- } else {
1260
- delete object.properties.outer;
1285
+ }
1286
+ if (pose.epoch) {
1287
+ object.epoch = pose.epoch;
1288
+ }
1289
+ if (pose.referencePlane) {
1290
+ object.referencePlane = pose.referencePlane;
1261
1291
  }
1262
1292
  }
1263
1293
  return cloned;
@@ -1298,10 +1328,59 @@ var WorldOrbit = (() => {
1298
1328
  }
1299
1329
  }
1300
1330
  function resolveProjection(document, projection) {
1301
- if (projection === "topdown" || projection === "isometric") {
1331
+ if (projection === "topdown" || projection === "isometric" || projection === "orthographic" || projection === "perspective") {
1302
1332
  return projection;
1303
1333
  }
1304
- return String(document.system?.properties.view ?? "topdown").toLowerCase() === "isometric" ? "isometric" : "topdown";
1334
+ const documentView = String(document.system?.properties.view ?? "topdown").toLowerCase();
1335
+ return parseViewProjection(documentView) ?? "topdown";
1336
+ }
1337
+ function resolveRenderProjection(projection, camera) {
1338
+ switch (projection) {
1339
+ case "topdown":
1340
+ return "topdown";
1341
+ case "isometric":
1342
+ return "isometric";
1343
+ case "orthographic":
1344
+ return camera && (camera.azimuth !== null || camera.elevation !== null || camera.roll !== null) ? "isometric" : "topdown";
1345
+ case "perspective":
1346
+ return "isometric";
1347
+ }
1348
+ }
1349
+ function normalizeViewCamera(camera) {
1350
+ if (!camera) {
1351
+ return null;
1352
+ }
1353
+ const normalized = {
1354
+ azimuth: normalizeFiniteCameraValue(camera.azimuth),
1355
+ elevation: normalizeFiniteCameraValue(camera.elevation),
1356
+ roll: normalizeFiniteCameraValue(camera.roll),
1357
+ distance: normalizePositiveCameraDistance(camera.distance)
1358
+ };
1359
+ return normalized.azimuth !== null || normalized.elevation !== null || normalized.roll !== null || normalized.distance !== null ? normalized : null;
1360
+ }
1361
+ function normalizeFiniteCameraValue(value) {
1362
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1363
+ }
1364
+ function normalizePositiveCameraDistance(value) {
1365
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
1366
+ }
1367
+ function buildSceneSubtitle(projection, renderProjection, layoutPreset, camera) {
1368
+ const parts = [`${capitalizeLabel(projection)} view`, `${capitalizeLabel(layoutPreset)} layout`];
1369
+ if (projection !== renderProjection) {
1370
+ parts.push(`2D ${renderProjection} fallback`);
1371
+ }
1372
+ if (camera) {
1373
+ const cameraParts = [
1374
+ camera.azimuth !== null ? `az ${camera.azimuth}` : null,
1375
+ camera.elevation !== null ? `el ${camera.elevation}` : null,
1376
+ camera.roll !== null ? `roll ${camera.roll}` : null,
1377
+ camera.distance !== null ? `dist ${camera.distance}` : null
1378
+ ].filter(Boolean);
1379
+ if (cameraParts.length > 0) {
1380
+ parts.push(`camera ${cameraParts.join(" / ")}`);
1381
+ }
1382
+ }
1383
+ return parts.join(" - ");
1305
1384
  }
1306
1385
  function resolveScaleModel(layoutPreset, overrides) {
1307
1386
  const defaults = defaultScaleModel(layoutPreset);
@@ -1737,6 +1816,8 @@ var WorldOrbit = (() => {
1737
1816
  function createGeneratedOverviewViewpoint(document, projection, preset) {
1738
1817
  const title = document.system?.title ?? document.system?.properties.title;
1739
1818
  const label = title ? `${String(title)} Overview` : "Overview";
1819
+ const camera = normalizeViewCamera(null);
1820
+ const renderProjection = resolveRenderProjection(projection, camera);
1740
1821
  return {
1741
1822
  id: "overview",
1742
1823
  label,
@@ -1745,6 +1826,8 @@ var WorldOrbit = (() => {
1745
1826
  selectedObjectId: null,
1746
1827
  eventIds: [],
1747
1828
  projection,
1829
+ renderProjection,
1830
+ camera,
1748
1831
  preset,
1749
1832
  rotationDeg: 0,
1750
1833
  scale: null,
@@ -1794,6 +1877,30 @@ var WorldOrbit = (() => {
1794
1877
  case "angle":
1795
1878
  draft.rotationDeg = parseFiniteNumber(normalizedValue) ?? draft.rotationDeg ?? 0;
1796
1879
  return;
1880
+ case "camera.azimuth":
1881
+ draft.camera = {
1882
+ ...draft.camera ?? createEmptyViewCamera(),
1883
+ azimuth: parseFiniteNumber(normalizedValue)
1884
+ };
1885
+ return;
1886
+ case "camera.elevation":
1887
+ draft.camera = {
1888
+ ...draft.camera ?? createEmptyViewCamera(),
1889
+ elevation: parseFiniteNumber(normalizedValue)
1890
+ };
1891
+ return;
1892
+ case "camera.roll":
1893
+ draft.camera = {
1894
+ ...draft.camera ?? createEmptyViewCamera(),
1895
+ roll: parseFiniteNumber(normalizedValue)
1896
+ };
1897
+ return;
1898
+ case "camera.distance":
1899
+ draft.camera = {
1900
+ ...draft.camera ?? createEmptyViewCamera(),
1901
+ distance: parsePositiveNumber(normalizedValue)
1902
+ };
1903
+ return;
1797
1904
  case "zoom":
1798
1905
  case "scale":
1799
1906
  draft.scale = parsePositiveNumber(normalizedValue);
@@ -1833,6 +1940,9 @@ var WorldOrbit = (() => {
1833
1940
  const selectedObjectId = draft.select && objectMap.has(draft.select) ? draft.select : objectId;
1834
1941
  const filter = normalizeViewpointFilter(draft.filter);
1835
1942
  const label = draft.label?.trim() || humanizeIdentifier(draft.id);
1943
+ const resolvedProjection = draft.projection ?? projection;
1944
+ const camera = normalizeViewCamera(draft.camera ?? null);
1945
+ const renderProjection = resolveRenderProjection(resolvedProjection, camera);
1836
1946
  return {
1837
1947
  id: draft.id,
1838
1948
  label,
@@ -1840,7 +1950,9 @@ var WorldOrbit = (() => {
1840
1950
  objectId,
1841
1951
  selectedObjectId,
1842
1952
  eventIds: [...new Set(draft.eventIds ?? [])],
1843
- projection: draft.projection ?? projection,
1953
+ projection: resolvedProjection,
1954
+ renderProjection,
1955
+ camera,
1844
1956
  preset: draft.preset ?? preset,
1845
1957
  rotationDeg: draft.rotationDeg ?? 0,
1846
1958
  scale: draft.scale ?? null,
@@ -1857,6 +1969,14 @@ var WorldOrbit = (() => {
1857
1969
  groupIds: []
1858
1970
  };
1859
1971
  }
1972
+ function createEmptyViewCamera() {
1973
+ return {
1974
+ azimuth: null,
1975
+ elevation: null,
1976
+ roll: null,
1977
+ distance: null
1978
+ };
1979
+ }
1860
1980
  function normalizeViewpointFilter(filter) {
1861
1981
  if (!filter) {
1862
1982
  return null;
@@ -1870,7 +1990,18 @@ var WorldOrbit = (() => {
1870
1990
  return normalized.query || normalized.objectTypes.length > 0 || normalized.tags.length > 0 || normalized.groupIds.length > 0 ? normalized : null;
1871
1991
  }
1872
1992
  function parseViewProjection(value) {
1873
- return value.toLowerCase() === "isometric" ? "isometric" : value.toLowerCase() === "topdown" ? "topdown" : null;
1993
+ switch (value.toLowerCase()) {
1994
+ case "topdown":
1995
+ return "topdown";
1996
+ case "isometric":
1997
+ return "isometric";
1998
+ case "orthographic":
1999
+ return "orthographic";
2000
+ case "perspective":
2001
+ return "perspective";
2002
+ default:
2003
+ return null;
2004
+ }
1874
2005
  }
1875
2006
  function parseRenderPreset(value) {
1876
2007
  const normalized = value.toLowerCase();
@@ -1908,7 +2039,7 @@ var WorldOrbit = (() => {
1908
2039
  }
1909
2040
  function parseViewpointGroups(value, document, relationships, objectMap) {
1910
2041
  return splitListValue(value).map((entry) => {
1911
- if (document.schemaVersion === "2.1" || document.groups.some((group) => group.id === entry)) {
2042
+ if (document.schemaVersion === "2.1" || document.schemaVersion === "2.5" || document.groups.some((group) => group.id === entry)) {
1912
2043
  return entry;
1913
2044
  }
1914
2045
  if (entry.startsWith("wo-") && entry.endsWith("-group")) {
@@ -2701,8 +2832,8 @@ var WorldOrbit = (() => {
2701
2832
  }
2702
2833
  return {
2703
2834
  format: "worldorbit",
2704
- version: "2.0",
2705
- schemaVersion: "2.0",
2835
+ version: "2.5",
2836
+ schemaVersion: "2.5",
2706
2837
  sourceVersion: document.version,
2707
2838
  system,
2708
2839
  groups: structuredClone(document.groups ?? []),
@@ -2761,8 +2892,9 @@ var WorldOrbit = (() => {
2761
2892
  };
2762
2893
  }
2763
2894
  function createDraftDefaults(document, preset, projection) {
2895
+ const rawView = typeof document.system?.properties.view === "string" ? document.system.properties.view.toLowerCase() : null;
2764
2896
  return {
2765
- view: typeof document.system?.properties.view === "string" && document.system.properties.view.toLowerCase() === "topdown" ? "topdown" : projection,
2897
+ view: rawView === "topdown" || rawView === "isometric" || rawView === "orthographic" || rawView === "perspective" ? rawView : projection,
2766
2898
  scale: typeof document.system?.properties.scale === "string" ? document.system.properties.scale : null,
2767
2899
  units: typeof document.system?.properties.units === "string" ? document.system.properties.units : null,
2768
2900
  preset,
@@ -2869,6 +3001,7 @@ var WorldOrbit = (() => {
2869
3001
  preset: viewpoint.preset,
2870
3002
  zoom: viewpoint.scale,
2871
3003
  rotationDeg: viewpoint.rotationDeg,
3004
+ camera: viewpoint.camera ? { ...viewpoint.camera } : null,
2872
3005
  layers: { ...viewpoint.layers },
2873
3006
  filter: viewpoint.filter ? {
2874
3007
  query: viewpoint.filter.query,
@@ -2910,7 +3043,9 @@ var WorldOrbit = (() => {
2910
3043
  objectId: pose.objectId,
2911
3044
  placement: clonePlacement(pose.placement),
2912
3045
  inner: pose.inner ? { ...pose.inner } : void 0,
2913
- outer: pose.outer ? { ...pose.outer } : void 0
3046
+ outer: pose.outer ? { ...pose.outer } : void 0,
3047
+ epoch: pose.epoch ?? null,
3048
+ referencePlane: pose.referencePlane ?? null
2914
3049
  };
2915
3050
  }
2916
3051
  function clonePlacement(placement) {
@@ -2925,21 +3060,42 @@ var WorldOrbit = (() => {
2925
3060
  return;
2926
3061
  }
2927
3062
  const objectMap = new Map(objects.map((object) => [object.id, object]));
3063
+ const referencedIds = /* @__PURE__ */ new Set([
3064
+ ...event.targetObjectId ? [event.targetObjectId] : [],
3065
+ ...event.participantObjectIds,
3066
+ ...event.positions.map((pose) => pose.objectId)
3067
+ ]);
3068
+ for (const objectId of referencedIds) {
3069
+ const object = objectMap.get(objectId);
3070
+ if (!object) {
3071
+ continue;
3072
+ }
3073
+ if (event.epoch) {
3074
+ object.epoch = event.epoch;
3075
+ }
3076
+ if (event.referencePlane) {
3077
+ object.referencePlane = event.referencePlane;
3078
+ }
3079
+ }
2928
3080
  for (const pose of event.positions) {
2929
3081
  const object = objectMap.get(pose.objectId);
2930
3082
  if (!object) {
2931
3083
  continue;
2932
3084
  }
2933
- object.placement = clonePlacement(pose.placement);
3085
+ if (pose.placement) {
3086
+ object.placement = clonePlacement(pose.placement);
3087
+ }
2934
3088
  if (pose.inner) {
2935
3089
  object.properties.inner = { ...pose.inner };
2936
- } else {
2937
- delete object.properties.inner;
2938
3090
  }
2939
3091
  if (pose.outer) {
2940
3092
  object.properties.outer = { ...pose.outer };
2941
- } else {
2942
- delete object.properties.outer;
3093
+ }
3094
+ if (pose.epoch) {
3095
+ object.epoch = pose.epoch;
3096
+ }
3097
+ if (pose.referencePlane) {
3098
+ object.referencePlane = pose.referencePlane;
2943
3099
  }
2944
3100
  }
2945
3101
  }
@@ -3024,6 +3180,18 @@ var WorldOrbit = (() => {
3024
3180
  if (viewpoint.rotationDeg !== 0) {
3025
3181
  info2[`${prefix}.rotation`] = String(viewpoint.rotationDeg);
3026
3182
  }
3183
+ if (viewpoint.camera?.azimuth !== null) {
3184
+ info2[`${prefix}.camera.azimuth`] = String(viewpoint.camera?.azimuth);
3185
+ }
3186
+ if (viewpoint.camera?.elevation !== null) {
3187
+ info2[`${prefix}.camera.elevation`] = String(viewpoint.camera?.elevation);
3188
+ }
3189
+ if (viewpoint.camera?.roll !== null) {
3190
+ info2[`${prefix}.camera.roll`] = String(viewpoint.camera?.roll);
3191
+ }
3192
+ if (viewpoint.camera?.distance !== null) {
3193
+ info2[`${prefix}.camera.distance`] = String(viewpoint.camera?.distance);
3194
+ }
3027
3195
  const serializedLayers = serializeViewpointLayers(viewpoint.layers);
3028
3196
  if (serializedLayers) {
3029
3197
  info2[`${prefix}.layers`] = serializedLayers;
@@ -3120,26 +3288,26 @@ var WorldOrbit = (() => {
3120
3288
  ];
3121
3289
  function formatDocument(document, options = {}) {
3122
3290
  const schema = options.schema ?? "auto";
3123
- const useDraft = schema === "2.0" || schema === "2.1" || schema === "2.0-draft" || document.version === "2.0" || document.version === "2.1" || document.version === "2.0-draft";
3291
+ const useDraft = schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.0-draft" || document.version === "2.0" || document.version === "2.1" || document.version === "2.5" || document.version === "2.0-draft";
3124
3292
  if (useDraft) {
3125
3293
  if (schema === "2.0-draft") {
3126
- const legacyDraftDocument = document.version === "2.0-draft" ? document : document.version === "2.0" || document.version === "2.1" ? {
3294
+ const legacyDraftDocument = document.version === "2.0-draft" ? document : document.version === "2.0" || document.version === "2.1" || document.version === "2.5" ? {
3127
3295
  ...document,
3128
3296
  version: "2.0-draft",
3129
3297
  schemaVersion: "2.0-draft"
3130
3298
  } : upgradeDocumentToDraftV2(document);
3131
3299
  return formatDraftDocument(legacyDraftDocument);
3132
3300
  }
3133
- const atlasDocument = document.version === "2.0" || document.version === "2.1" ? document : document.version === "2.0-draft" ? {
3301
+ const atlasDocument = document.version === "2.0" || document.version === "2.1" || document.version === "2.5" ? document : document.version === "2.0-draft" ? {
3134
3302
  ...document,
3135
3303
  version: "2.0",
3136
3304
  schemaVersion: "2.0"
3137
3305
  } : upgradeDocumentToV2(document);
3138
- if (schema === "2.1" && atlasDocument.version !== "2.1") {
3306
+ if ((schema === "2.0" || schema === "2.1" || schema === "2.5") && atlasDocument.version !== schema) {
3139
3307
  return formatAtlasDocument({
3140
3308
  ...atlasDocument,
3141
- version: "2.1",
3142
- schemaVersion: "2.1"
3309
+ version: schema,
3310
+ schemaVersion: schema
3143
3311
  });
3144
3312
  }
3145
3313
  return formatAtlasDocument(atlasDocument);
@@ -3416,6 +3584,21 @@ var WorldOrbit = (() => {
3416
3584
  if (viewpoint.rotationDeg !== 0) {
3417
3585
  lines.push(` rotation ${viewpoint.rotationDeg}`);
3418
3586
  }
3587
+ if (viewpoint.camera && hasCameraValues(viewpoint.camera)) {
3588
+ lines.push(" camera");
3589
+ if (viewpoint.camera.azimuth !== null) {
3590
+ lines.push(` azimuth ${viewpoint.camera.azimuth}`);
3591
+ }
3592
+ if (viewpoint.camera.elevation !== null) {
3593
+ lines.push(` elevation ${viewpoint.camera.elevation}`);
3594
+ }
3595
+ if (viewpoint.camera.roll !== null) {
3596
+ lines.push(` roll ${viewpoint.camera.roll}`);
3597
+ }
3598
+ if (viewpoint.camera.distance !== null) {
3599
+ lines.push(` distance ${viewpoint.camera.distance}`);
3600
+ }
3601
+ }
3419
3602
  const layerTokens = formatDraftLayers(viewpoint.layers);
3420
3603
  if (layerTokens.length > 0) {
3421
3604
  lines.push(` layers ${layerTokens.join(" ")}`);
@@ -3515,6 +3698,12 @@ var WorldOrbit = (() => {
3515
3698
  if (event.visibility) {
3516
3699
  lines.push(` visibility ${quoteIfNeeded(event.visibility)}`);
3517
3700
  }
3701
+ if (event.epoch) {
3702
+ lines.push(` epoch ${quoteIfNeeded(event.epoch)}`);
3703
+ }
3704
+ if (event.referencePlane) {
3705
+ lines.push(` referencePlane ${quoteIfNeeded(event.referencePlane)}`);
3706
+ }
3518
3707
  if (event.tags.length > 0) {
3519
3708
  lines.push(` tags ${event.tags.map(quoteIfNeeded).join(" ")}`);
3520
3709
  }
@@ -3539,10 +3728,15 @@ var WorldOrbit = (() => {
3539
3728
  function formatEventPoseFields(pose) {
3540
3729
  return [
3541
3730
  ...formatPlacement(pose.placement),
3731
+ ...pose.epoch ? [`epoch ${quoteIfNeeded(pose.epoch)}`] : [],
3732
+ ...pose.referencePlane ? [`referencePlane ${quoteIfNeeded(pose.referencePlane)}`] : [],
3542
3733
  ...formatOptionalUnit("inner", pose.inner),
3543
3734
  ...formatOptionalUnit("outer", pose.outer)
3544
3735
  ];
3545
3736
  }
3737
+ function hasCameraValues(camera) {
3738
+ return camera.azimuth !== null || camera.elevation !== null || camera.roll !== null || camera.distance !== null;
3739
+ }
3546
3740
  function formatValue(value) {
3547
3741
  if (Array.isArray(value)) {
3548
3742
  return value.map((item) => quoteIfNeeded(item)).join(" ");
@@ -3836,13 +4030,13 @@ var WorldOrbit = (() => {
3836
4030
  validateRelation(relation, objectMap, diagnostics);
3837
4031
  }
3838
4032
  for (const viewpoint of document.system?.viewpoints ?? []) {
3839
- validateViewpoint(viewpoint.filter, viewpoint.events ?? [], groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpoint.id);
4033
+ validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap);
3840
4034
  }
3841
4035
  for (const object of document.objects) {
3842
4036
  validateObject(object, document.system, objectMap, groupIds, diagnostics);
3843
4037
  }
3844
4038
  for (const event of document.events) {
3845
- validateEvent(event, objectMap, diagnostics);
4039
+ validateEvent(event, document.system, objectMap, diagnostics);
3846
4040
  }
3847
4041
  return diagnostics;
3848
4042
  }
@@ -3861,21 +4055,24 @@ var WorldOrbit = (() => {
3861
4055
  diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
3862
4056
  }
3863
4057
  }
3864
- function validateViewpoint(filter, eventRefs, groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpointId) {
3865
- if (sourceSchemaVersion === "2.1") {
4058
+ function validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap) {
4059
+ const filter = viewpoint.filter;
4060
+ if (sourceSchemaVersion === "2.1" || sourceSchemaVersion === "2.5") {
3866
4061
  if (filter) {
3867
4062
  for (const groupId of filter.groupIds) {
3868
4063
  if (!groupIds.has(groupId)) {
3869
- diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.groups`));
4064
+ diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.groups`));
3870
4065
  }
3871
4066
  }
3872
4067
  }
3873
- for (const eventId of eventRefs) {
4068
+ for (const eventId of viewpoint.events ?? []) {
3874
4069
  if (!eventIds.has(eventId)) {
3875
- diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.events`));
4070
+ diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.events`));
3876
4071
  }
3877
4072
  }
3878
4073
  }
4074
+ validateProjection(viewpoint.projection, diagnostics, `viewpoint.${viewpoint.id}.projection`, viewpoint.id);
4075
+ validateCamera(viewpoint.camera, viewpoint.projection, viewpoint.rotationDeg, diagnostics, viewpoint.id, viewpoint.focusObjectId, viewpoint.selectedObjectId, filter, objectMap);
3879
4076
  }
3880
4077
  function validateObject(object, system, objectMap, groupIds, diagnostics) {
3881
4078
  const placement = object.placement;
@@ -3888,6 +4085,12 @@ var WorldOrbit = (() => {
3888
4085
  }
3889
4086
  }
3890
4087
  }
4088
+ if (typeof object.epoch === "string" && !object.epoch.trim()) {
4089
+ diagnostics.push(warn("validate.epoch.empty", `Object "${object.id}" defines an empty epoch string.`, object.id, "epoch"));
4090
+ }
4091
+ if (typeof object.referencePlane === "string" && !object.referencePlane.trim()) {
4092
+ diagnostics.push(warn("validate.referencePlane.empty", `Object "${object.id}" defines an empty reference plane string.`, object.id, "referencePlane"));
4093
+ }
3891
4094
  if (orbitPlacement) {
3892
4095
  if (!objectMap.has(orbitPlacement.target)) {
3893
4096
  diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
@@ -3959,12 +4162,18 @@ var WorldOrbit = (() => {
3959
4162
  }
3960
4163
  }
3961
4164
  }
3962
- function validateEvent(event, objectMap, diagnostics) {
4165
+ function validateEvent(event, system, objectMap, diagnostics) {
3963
4166
  const fieldPrefix = `event.${event.id}`;
3964
4167
  const referencedIds = /* @__PURE__ */ new Set();
3965
4168
  if (!event.kind.trim()) {
3966
4169
  diagnostics.push(error("validate.event.kind.required", `Event "${event.id}" is missing a "kind" value.`, void 0, `${fieldPrefix}.kind`));
3967
4170
  }
4171
+ if (typeof event.epoch === "string" && !event.epoch.trim()) {
4172
+ diagnostics.push(warn("validate.event.epoch.empty", `Event "${event.id}" defines an empty epoch string.`, void 0, `${fieldPrefix}.epoch`));
4173
+ }
4174
+ if (typeof event.referencePlane === "string" && !event.referencePlane.trim()) {
4175
+ diagnostics.push(warn("validate.event.referencePlane.empty", `Event "${event.id}" defines an empty reference plane string.`, void 0, `${fieldPrefix}.referencePlane`));
4176
+ }
3968
4177
  if (!event.targetObjectId && event.participantObjectIds.length === 0) {
3969
4178
  diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
3970
4179
  }
@@ -4011,10 +4220,14 @@ var WorldOrbit = (() => {
4011
4220
  if (!referencedIds.has(pose.objectId)) {
4012
4221
  diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
4013
4222
  }
4014
- validateEventPose(pose, object, objectMap, diagnostics, poseFieldPrefix, event.id);
4223
+ validateEventPose(pose, object, event, system, objectMap, diagnostics, poseFieldPrefix, event.id);
4224
+ }
4225
+ const missingPoseIds = [...referencedIds].filter((objectId) => !poseIds.has(objectId));
4226
+ if (event.positions.length > 0 && missingPoseIds.length > 0) {
4227
+ diagnostics.push(warn("validate.event.positions.partial", `Event "${event.id}" leaves ${missingPoseIds.length} referenced object(s) on their base placement.`, void 0, `${fieldPrefix}.positions`));
4015
4228
  }
4016
4229
  }
4017
- function validateEventPose(pose, object, objectMap, diagnostics, fieldPrefix, eventId) {
4230
+ function validateEventPose(pose, object, event, system, objectMap, diagnostics, fieldPrefix, eventId) {
4018
4231
  const placement = pose.placement;
4019
4232
  if (!placement) {
4020
4233
  diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
@@ -4027,6 +4240,15 @@ var WorldOrbit = (() => {
4027
4240
  if (placement.distance && placement.semiMajor) {
4028
4241
  diagnostics.push(error("validate.event.pose.orbit.distanceConflict", `Event "${eventId}" pose "${pose.objectId}" cannot declare both "distance" and "semiMajor".`, void 0, `${fieldPrefix}.distance`));
4029
4242
  }
4243
+ if (placement.phase && !resolveEffectiveEpoch(system, object, event, pose)) {
4244
+ diagnostics.push(warn("validate.event.pose.phase.epochMissing", `Event "${eventId}" pose "${pose.objectId}" sets "phase" without an effective epoch.`, void 0, `${fieldPrefix}.phase`));
4245
+ }
4246
+ if (placement.inclination && !resolveEffectiveReferencePlane(system, object, event, pose)) {
4247
+ diagnostics.push(warn("validate.event.pose.inclination.referencePlaneMissing", `Event "${eventId}" pose "${pose.objectId}" sets "inclination" without an effective reference plane.`, void 0, `${fieldPrefix}.inclination`));
4248
+ }
4249
+ if (placement.period && !massInSolar(objectMap.get(placement.target)?.properties.mass)) {
4250
+ 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`));
4251
+ }
4030
4252
  return;
4031
4253
  }
4032
4254
  if (placement.mode === "surface") {
@@ -4161,6 +4383,52 @@ var WorldOrbit = (() => {
4161
4383
  return null;
4162
4384
  }
4163
4385
  }
4386
+ function validateProjection(projection, diagnostics, field, viewpointId) {
4387
+ if (projection !== "topdown" && projection !== "isometric" && projection !== "orthographic" && projection !== "perspective") {
4388
+ diagnostics.push(error("validate.viewpoint.projection.invalid", `Unknown projection "${String(projection)}" in viewpoint "${viewpointId}".`, void 0, field));
4389
+ }
4390
+ }
4391
+ function validateCamera(camera, projection, rotationDeg, diagnostics, viewpointId, focusObjectId, selectedObjectId, filter, objectMap) {
4392
+ if (!camera) {
4393
+ return;
4394
+ }
4395
+ const prefix = `viewpoint.${viewpointId}.camera`;
4396
+ for (const [key, value] of [
4397
+ ["azimuth", camera.azimuth],
4398
+ ["elevation", camera.elevation],
4399
+ ["roll", camera.roll],
4400
+ ["distance", camera.distance]
4401
+ ]) {
4402
+ if (value !== null && (!Number.isFinite(value) || key === "distance" && value <= 0)) {
4403
+ diagnostics.push(error("validate.viewpoint.camera.invalid", `Invalid camera ${key} "${String(value)}" in viewpoint "${viewpointId}".`, void 0, `${prefix}.${key}`));
4404
+ }
4405
+ }
4406
+ if (camera.distance !== null && projection !== "perspective") {
4407
+ 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`));
4408
+ }
4409
+ if (projection === "topdown" && (camera.elevation !== null || camera.roll !== null)) {
4410
+ 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));
4411
+ }
4412
+ if (projection === "isometric" && camera.elevation !== null) {
4413
+ diagnostics.push(info("validate.viewpoint.camera.isometricStored", `Camera elevation on isometric viewpoint "${viewpointId}" is preserved semantically for future 3D rendering.`, void 0, `${prefix}.elevation`));
4414
+ }
4415
+ if (camera.azimuth !== null && camera.azimuth !== 0 && rotationDeg !== 0) {
4416
+ 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`));
4417
+ }
4418
+ const hasAnchor = focusObjectId !== null && objectMap.has(focusObjectId) || selectedObjectId !== null && objectMap.has(selectedObjectId) || !!filter;
4419
+ if (!hasAnchor) {
4420
+ diagnostics.push(info("validate.viewpoint.camera.anchorMissing", `Viewpoint "${viewpointId}" stores camera settings without a focus object, selection, or filter anchor.`, void 0, prefix));
4421
+ }
4422
+ }
4423
+ function resolveEffectiveEpoch(system, object, event, pose) {
4424
+ return normalizeOptionalContextString(pose?.epoch) ?? normalizeOptionalContextString(event?.epoch) ?? normalizeOptionalContextString(object.epoch) ?? normalizeOptionalContextString(system?.epoch) ?? null;
4425
+ }
4426
+ function resolveEffectiveReferencePlane(system, object, event, pose) {
4427
+ return normalizeOptionalContextString(pose?.referencePlane) ?? normalizeOptionalContextString(event?.referencePlane) ?? normalizeOptionalContextString(object.referencePlane) ?? normalizeOptionalContextString(system?.referencePlane) ?? null;
4428
+ }
4429
+ function normalizeOptionalContextString(value) {
4430
+ return typeof value === "string" && value.trim() ? value.trim() : null;
4431
+ }
4164
4432
  function toleranceForField(object, field) {
4165
4433
  const tolerance = object.tolerances?.find((entry) => entry.field === field)?.value;
4166
4434
  if (typeof tolerance === "number") {
@@ -4269,7 +4537,9 @@ var WorldOrbit = (() => {
4269
4537
  "surface",
4270
4538
  "free",
4271
4539
  "inner",
4272
- "outer"
4540
+ "outer",
4541
+ "epoch",
4542
+ "referencePlane"
4273
4543
  ]);
4274
4544
  function parseWorldOrbitAtlas(source) {
4275
4545
  return parseAtlasSource(source);
@@ -4314,7 +4584,7 @@ var WorldOrbit = (() => {
4314
4584
  if (!sawSchemaHeader) {
4315
4585
  sourceSchemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
4316
4586
  sawSchemaHeader = true;
4317
- if (prepared.comments.length > 0 && sourceSchemaVersion !== "2.1") {
4587
+ if (prepared.comments.length > 0 && isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
4318
4588
  diagnostics.push({
4319
4589
  code: "parse.schema21.commentCompatibility",
4320
4590
  severity: "warning",
@@ -4384,11 +4654,11 @@ var WorldOrbit = (() => {
4384
4654
  return document;
4385
4655
  }
4386
4656
  function assertDraftSchemaHeader(tokens, line) {
4387
- if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1"].includes(tokens[1].value.toLowerCase())) {
4388
- throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
4657
+ if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1", "2.5"].includes(tokens[1].value.toLowerCase())) {
4658
+ 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);
4389
4659
  }
4390
4660
  const version = tokens[1].value.toLowerCase();
4391
- return version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
4661
+ return version === "2.5" ? "2.5" : version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
4392
4662
  }
4393
4663
  function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
4394
4664
  const keyword = tokens[0]?.value.toLowerCase();
@@ -4408,6 +4678,8 @@ var WorldOrbit = (() => {
4408
4678
  return {
4409
4679
  kind: "defaults",
4410
4680
  system,
4681
+ sourceSchemaVersion,
4682
+ diagnostics,
4411
4683
  seenFields: /* @__PURE__ */ new Set()
4412
4684
  };
4413
4685
  case "atlas":
@@ -4500,6 +4772,7 @@ var WorldOrbit = (() => {
4500
4772
  preset: system.defaults.preset,
4501
4773
  zoom: null,
4502
4774
  rotationDeg: 0,
4775
+ camera: null,
4503
4776
  layers: {},
4504
4777
  filter: null
4505
4778
  };
@@ -4513,7 +4786,10 @@ var WorldOrbit = (() => {
4513
4786
  seenFields: /* @__PURE__ */ new Set(),
4514
4787
  inFilter: false,
4515
4788
  filterIndent: null,
4516
- seenFilterFields: /* @__PURE__ */ new Set()
4789
+ seenFilterFields: /* @__PURE__ */ new Set(),
4790
+ inCamera: false,
4791
+ cameraIndent: null,
4792
+ seenCameraFields: /* @__PURE__ */ new Set()
4517
4793
  };
4518
4794
  }
4519
4795
  function startAnnotationSection(tokens, line, system, annotationIds) {
@@ -4620,6 +4896,8 @@ var WorldOrbit = (() => {
4620
4896
  participantObjectIds: [],
4621
4897
  timing: null,
4622
4898
  visibility: null,
4899
+ epoch: null,
4900
+ referencePlane: null,
4623
4901
  tags: [],
4624
4902
  color: null,
4625
4903
  hidden: false,
@@ -4744,6 +5022,12 @@ var WorldOrbit = (() => {
4744
5022
  const value = joinFieldValue(tokens, line);
4745
5023
  switch (key) {
4746
5024
  case "view":
5025
+ if (isSchema25Projection(value)) {
5026
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "defaults.view", {
5027
+ line,
5028
+ column: tokens[0].column
5029
+ });
5030
+ }
4747
5031
  section.system.defaults.view = parseProjectionValue(value, line, tokens[0].column);
4748
5032
  return;
4749
5033
  case "scale":
@@ -4783,14 +5067,36 @@ var WorldOrbit = (() => {
4783
5067
  throw new WorldOrbitError(`Unknown atlas field "${tokens[0].value}"`, line, tokens[0].column);
4784
5068
  }
4785
5069
  function applyViewpointField2(section, indent, tokens, line) {
5070
+ if (section.inCamera && indent <= (section.cameraIndent ?? 0)) {
5071
+ section.inCamera = false;
5072
+ section.cameraIndent = null;
5073
+ }
4786
5074
  if (section.inFilter && indent <= (section.filterIndent ?? 0)) {
4787
5075
  section.inFilter = false;
4788
5076
  section.filterIndent = null;
4789
5077
  }
5078
+ if (section.inCamera) {
5079
+ applyViewpointCameraField(section, tokens, line);
5080
+ return;
5081
+ }
4790
5082
  if (section.inFilter) {
4791
5083
  applyViewpointFilterField(section, tokens, line);
4792
5084
  return;
4793
5085
  }
5086
+ if (tokens.length === 1 && tokens[0].value.toLowerCase() === "camera") {
5087
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
5088
+ line,
5089
+ column: tokens[0].column
5090
+ });
5091
+ if (section.seenFields.has("camera")) {
5092
+ throw new WorldOrbitError('Duplicate viewpoint field "camera"', line, tokens[0].column);
5093
+ }
5094
+ section.seenFields.add("camera");
5095
+ section.inCamera = true;
5096
+ section.cameraIndent = indent;
5097
+ section.viewpoint.camera = section.viewpoint.camera ?? createEmptyViewCamera2();
5098
+ return;
5099
+ }
4794
5100
  if (tokens.length === 1 && tokens[0].value.toLowerCase() === "filter") {
4795
5101
  if (section.seenFields.has("filter")) {
4796
5102
  throw new WorldOrbitError('Duplicate viewpoint field "filter"', line, tokens[0].column);
@@ -4816,6 +5122,12 @@ var WorldOrbit = (() => {
4816
5122
  section.viewpoint.selectedObjectId = value;
4817
5123
  return;
4818
5124
  case "projection":
5125
+ if (isSchema25Projection(value)) {
5126
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "projection", {
5127
+ line,
5128
+ column: tokens[0].column
5129
+ });
5130
+ }
4819
5131
  section.viewpoint.projection = parseProjectionValue(value, line, tokens[0].column);
4820
5132
  return;
4821
5133
  case "preset":
@@ -4827,6 +5139,13 @@ var WorldOrbit = (() => {
4827
5139
  case "rotation":
4828
5140
  section.viewpoint.rotationDeg = parseFiniteNumber2(value, line, tokens[0].column, "rotation");
4829
5141
  return;
5142
+ case "camera":
5143
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
5144
+ line,
5145
+ column: tokens[0].column
5146
+ });
5147
+ section.viewpoint.camera = parseInlineViewCamera(tokens.slice(1), line, section.viewpoint.camera);
5148
+ return;
4830
5149
  case "layers":
4831
5150
  section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line, section.sourceSchemaVersion, section.diagnostics);
4832
5151
  return;
@@ -4841,6 +5160,28 @@ var WorldOrbit = (() => {
4841
5160
  throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
4842
5161
  }
4843
5162
  }
5163
+ function applyViewpointCameraField(section, tokens, line) {
5164
+ const key = requireUniqueField(tokens, section.seenCameraFields, line);
5165
+ const value = joinFieldValue(tokens, line);
5166
+ const camera = section.viewpoint.camera ?? createEmptyViewCamera2();
5167
+ switch (key) {
5168
+ case "azimuth":
5169
+ camera.azimuth = parseFiniteNumber2(value, line, tokens[0].column, "camera.azimuth");
5170
+ break;
5171
+ case "elevation":
5172
+ camera.elevation = parseFiniteNumber2(value, line, tokens[0].column, "camera.elevation");
5173
+ break;
5174
+ case "roll":
5175
+ camera.roll = parseFiniteNumber2(value, line, tokens[0].column, "camera.roll");
5176
+ break;
5177
+ case "distance":
5178
+ camera.distance = parsePositiveNumber2(value, line, tokens[0].column, "camera.distance");
5179
+ break;
5180
+ default:
5181
+ throw new WorldOrbitError(`Unknown viewpoint camera field "${tokens[0].value}"`, line, tokens[0].column);
5182
+ }
5183
+ section.viewpoint.camera = camera;
5184
+ }
4844
5185
  function applyViewpointFilterField(section, tokens, line) {
4845
5186
  const key = requireUniqueField(tokens, section.seenFilterFields, line);
4846
5187
  const filter = section.viewpoint.filter ?? createEmptyViewpointFilter2();
@@ -4951,6 +5292,12 @@ var WorldOrbit = (() => {
4951
5292
  section.positionsIndent = null;
4952
5293
  }
4953
5294
  if (section.activePose) {
5295
+ if (tokens[0]?.value === "epoch" || tokens[0]?.value === "referencePlane") {
5296
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, `pose.${tokens[0].value}`, {
5297
+ line,
5298
+ column: tokens[0]?.column ?? 1
5299
+ });
5300
+ }
4954
5301
  section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
4955
5302
  return;
4956
5303
  }
@@ -5005,6 +5352,20 @@ var WorldOrbit = (() => {
5005
5352
  case "visibility":
5006
5353
  section.event.visibility = joinFieldValue(tokens, line);
5007
5354
  return;
5355
+ case "epoch":
5356
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.epoch", {
5357
+ line,
5358
+ column: tokens[0].column
5359
+ });
5360
+ section.event.epoch = joinFieldValue(tokens, line);
5361
+ return;
5362
+ case "referenceplane":
5363
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.referencePlane", {
5364
+ line,
5365
+ column: tokens[0].column
5366
+ });
5367
+ section.event.referencePlane = joinFieldValue(tokens, line);
5368
+ return;
5008
5369
  case "tags":
5009
5370
  section.event.tags = parseTokenList(tokens.slice(1), line, "tags");
5010
5371
  return;
@@ -5132,11 +5493,15 @@ var WorldOrbit = (() => {
5132
5493
  }
5133
5494
  function parseProjectionValue(value, line, column) {
5134
5495
  const normalized = value.toLowerCase();
5135
- if (normalized !== "topdown" && normalized !== "isometric") {
5496
+ if (normalized !== "topdown" && normalized !== "isometric" && normalized !== "orthographic" && normalized !== "perspective") {
5136
5497
  throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
5137
5498
  }
5138
5499
  return normalized;
5139
5500
  }
5501
+ function isSchema25Projection(value) {
5502
+ const normalized = value.toLowerCase();
5503
+ return normalized === "orthographic" || normalized === "perspective";
5504
+ }
5140
5505
  function parsePresetValue(value, line, column) {
5141
5506
  const normalized = value.toLowerCase();
5142
5507
  if (normalized === "diagram" || normalized === "presentation" || normalized === "atlas-card" || normalized === "markdown") {
@@ -5166,6 +5531,48 @@ var WorldOrbit = (() => {
5166
5531
  groupIds: []
5167
5532
  };
5168
5533
  }
5534
+ function createEmptyViewCamera2() {
5535
+ return {
5536
+ azimuth: null,
5537
+ elevation: null,
5538
+ roll: null,
5539
+ distance: null
5540
+ };
5541
+ }
5542
+ function parseInlineViewCamera(tokens, line, current) {
5543
+ if (tokens.length === 0 || tokens.length % 2 !== 0) {
5544
+ throw new WorldOrbitError('Field "camera" expects "<field> <value>" pairs', line, tokens[0]?.column ?? 1);
5545
+ }
5546
+ const camera = current ? { ...current } : createEmptyViewCamera2();
5547
+ const seen = /* @__PURE__ */ new Set();
5548
+ for (let index = 0; index < tokens.length; index += 2) {
5549
+ const fieldToken = tokens[index];
5550
+ const valueToken = tokens[index + 1];
5551
+ const key = fieldToken.value.toLowerCase();
5552
+ if (seen.has(key)) {
5553
+ throw new WorldOrbitError(`Duplicate viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
5554
+ }
5555
+ seen.add(key);
5556
+ const value = valueToken.value;
5557
+ switch (key) {
5558
+ case "azimuth":
5559
+ camera.azimuth = parseFiniteNumber2(value, line, fieldToken.column, "camera.azimuth");
5560
+ break;
5561
+ case "elevation":
5562
+ camera.elevation = parseFiniteNumber2(value, line, fieldToken.column, "camera.elevation");
5563
+ break;
5564
+ case "roll":
5565
+ camera.roll = parseFiniteNumber2(value, line, fieldToken.column, "camera.roll");
5566
+ break;
5567
+ case "distance":
5568
+ camera.distance = parsePositiveNumber2(value, line, fieldToken.column, "camera.distance");
5569
+ break;
5570
+ default:
5571
+ throw new WorldOrbitError(`Unknown viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
5572
+ }
5573
+ }
5574
+ return camera;
5575
+ }
5169
5576
  function parseInlineObjectFields(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
5170
5577
  const fields = [];
5171
5578
  let index = 0;
@@ -5298,7 +5705,7 @@ var WorldOrbit = (() => {
5298
5705
  object.tolerances = tolerances;
5299
5706
  if (typedBlocks && Object.keys(typedBlocks).length > 0)
5300
5707
  object.typedBlocks = typedBlocks;
5301
- if (sourceSchemaVersion !== "2.1") {
5708
+ if (isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
5302
5709
  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) {
5303
5710
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
5304
5711
  }
@@ -5314,23 +5721,25 @@ var WorldOrbit = (() => {
5314
5721
  };
5315
5722
  }
5316
5723
  function normalizeDraftEventPose(rawPose) {
5317
- const fieldMap = collectDraftFields(rawPose.fields);
5724
+ const fieldMap = collectDraftFields(rawPose.fields, "event-pose");
5318
5725
  const placement = extractPlacementFromFieldMap(fieldMap);
5319
5726
  return {
5320
5727
  objectId: rawPose.objectId,
5321
5728
  placement,
5322
5729
  inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
5323
- outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer")
5730
+ outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer"),
5731
+ epoch: parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]),
5732
+ referencePlane: parseOptionalJoinedValue(fieldMap.get("referencePlane")?.[0])
5324
5733
  };
5325
5734
  }
5326
- function collectDraftFields(fields) {
5735
+ function collectDraftFields(fields, _mode = "object") {
5327
5736
  const grouped = /* @__PURE__ */ new Map();
5328
5737
  for (const field of fields) {
5329
5738
  const spec = getDraftObjectFieldSpec(field.key);
5330
- if (!spec) {
5739
+ if (!spec && !EVENT_POSE_FIELD_KEYS.has(field.key)) {
5331
5740
  throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
5332
5741
  }
5333
- if (!spec.allowRepeat && grouped.has(field.key)) {
5742
+ if (!spec?.allowRepeat && grouped.has(field.key)) {
5334
5743
  throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
5335
5744
  }
5336
5745
  const existing = grouped.get(field.key) ?? [];
@@ -5507,7 +5916,7 @@ var WorldOrbit = (() => {
5507
5916
  }
5508
5917
  }
5509
5918
  function warnIfSchema21Feature(sourceSchemaVersion, diagnostics, featureName, location) {
5510
- if (sourceSchemaVersion === "2.1") {
5919
+ if (!isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
5511
5920
  return;
5512
5921
  }
5513
5922
  diagnostics.push({
@@ -5519,6 +5928,34 @@ var WorldOrbit = (() => {
5519
5928
  column: location.column
5520
5929
  });
5521
5930
  }
5931
+ function warnIfSchema25Feature(sourceSchemaVersion, diagnostics, featureName, location) {
5932
+ if (!isSchemaOlderThan(sourceSchemaVersion, "2.5")) {
5933
+ return;
5934
+ }
5935
+ diagnostics.push({
5936
+ code: "parse.schema25.featureCompatibility",
5937
+ severity: "warning",
5938
+ source: "parse",
5939
+ message: `Feature "${featureName}" requires schema 2.5; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
5940
+ line: location.line,
5941
+ column: location.column
5942
+ });
5943
+ }
5944
+ function isSchemaOlderThan(sourceSchemaVersion, requiredVersion) {
5945
+ return schemaVersionRank(sourceSchemaVersion) < schemaVersionRank(requiredVersion);
5946
+ }
5947
+ function schemaVersionRank(version) {
5948
+ switch (version) {
5949
+ case "2.0-draft":
5950
+ return 0;
5951
+ case "2.0":
5952
+ return 1;
5953
+ case "2.1":
5954
+ return 2;
5955
+ case "2.5":
5956
+ return 3;
5957
+ }
5958
+ }
5522
5959
  function preprocessAtlasSource(source) {
5523
5960
  const chars = [...source];
5524
5961
  const comments = [];
@@ -5606,7 +6043,7 @@ var WorldOrbit = (() => {
5606
6043
  }
5607
6044
 
5608
6045
  // packages/core/dist/atlas-edit.js
5609
- function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.0") {
6046
+ function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.5") {
5610
6047
  return {
5611
6048
  format: "worldorbit",
5612
6049
  version,
@@ -5953,8 +6390,9 @@ var WorldOrbit = (() => {
5953
6390
  }
5954
6391
 
5955
6392
  // packages/core/dist/load.js
5956
- var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1)?$/i;
6393
+ var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1|\.5)?$/i;
5957
6394
  var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
6395
+ var ATLAS_SCHEMA_25_PATTERN = /^schema\s+2\.5$/i;
5958
6396
  var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
5959
6397
  function detectWorldOrbitSchemaVersion(source) {
5960
6398
  for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
@@ -5968,6 +6406,9 @@ var WorldOrbit = (() => {
5968
6406
  if (ATLAS_SCHEMA_21_PATTERN.test(trimmed)) {
5969
6407
  return "2.1";
5970
6408
  }
6409
+ if (ATLAS_SCHEMA_25_PATTERN.test(trimmed)) {
6410
+ return "2.5";
6411
+ }
5971
6412
  if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
5972
6413
  return "2.0";
5973
6414
  }
@@ -6028,7 +6469,7 @@ var WorldOrbit = (() => {
6028
6469
  }
6029
6470
  function loadWorldOrbitSourceWithDiagnostics(source) {
6030
6471
  const schemaVersion = detectWorldOrbitSchemaVersion(source);
6031
- if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1") {
6472
+ if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5") {
6032
6473
  return loadAtlasSourceWithDiagnostics(source, schemaVersion);
6033
6474
  }
6034
6475
  let ast;