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.
@@ -1101,7 +1101,9 @@ var WorldOrbit = (() => {
1101
1101
  const height = frame.height;
1102
1102
  const padding = frame.padding;
1103
1103
  const layoutPreset = resolveLayoutPreset(document2);
1104
- const projection = resolveProjection(document2, options.projection);
1104
+ const schemaProjection = resolveProjection(document2, options.projection);
1105
+ const camera = normalizeViewCamera(options.camera ?? null);
1106
+ const renderProjection = resolveRenderProjection(schemaProjection, camera);
1105
1107
  const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
1106
1108
  const spacingFactor = layoutPresetSpacing(layoutPreset);
1107
1109
  const systemId = document2.system?.id ?? null;
@@ -1144,7 +1146,7 @@ var WorldOrbit = (() => {
1144
1146
  surfaceChildren,
1145
1147
  objectMap,
1146
1148
  spacingFactor,
1147
- projection,
1149
+ projection: renderProjection,
1148
1150
  scaleModel
1149
1151
  };
1150
1152
  const primaryRoot = rootObjects.find((object) => object.type === "star") ?? rootObjects[0] ?? null;
@@ -1156,7 +1158,7 @@ var WorldOrbit = (() => {
1156
1158
  const rootRingRadius = Math.min(width, height) * 0.28 * spacingFactor * scaleModel.orbitDistanceMultiplier;
1157
1159
  secondaryRoots.forEach((object, index) => {
1158
1160
  const angle = angleForIndex(index, secondaryRoots.length, -Math.PI / 2);
1159
- const offset = projectPolarOffset(angle, rootRingRadius, projection, 1);
1161
+ const offset = projectPolarOffset(angle, rootRingRadius, renderProjection, 1);
1160
1162
  placeObject(object, centerX + offset.x, centerY + offset.y, 0, positions, orbitDrafts, leaderDrafts, context);
1161
1163
  });
1162
1164
  }
@@ -1218,27 +1220,34 @@ var WorldOrbit = (() => {
1218
1220
  const layers = createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels);
1219
1221
  const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, scaleModel.labelMultiplier);
1220
1222
  const semanticGroups = createSceneSemanticGroups(document2, objects);
1221
- const viewpoints = createSceneViewpoints(document2, projection, frame.preset, relationships, objectMap);
1223
+ const viewpoints = createSceneViewpoints(document2, schemaProjection, frame.preset, relationships, objectMap);
1222
1224
  const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, scaleModel.labelMultiplier);
1223
1225
  return {
1224
1226
  width,
1225
1227
  height,
1226
1228
  padding,
1227
1229
  renderPreset: frame.preset,
1228
- projection,
1230
+ projection: schemaProjection,
1231
+ renderProjection,
1232
+ camera,
1229
1233
  scaleModel,
1230
1234
  title: String(document2.system?.title ?? document2.system?.properties.title ?? document2.system?.id ?? "WorldOrbit") || "WorldOrbit",
1231
- subtitle: `${capitalizeLabel(projection)} view - ${capitalizeLabel(layoutPreset)} layout`,
1235
+ subtitle: buildSceneSubtitle(schemaProjection, renderProjection, layoutPreset, camera),
1232
1236
  systemId,
1233
- viewMode: projection,
1237
+ viewMode: schemaProjection,
1234
1238
  layoutPreset,
1235
1239
  metadata: {
1236
1240
  format: document2.format,
1237
1241
  version: document2.version,
1238
- view: projection,
1242
+ view: schemaProjection,
1243
+ renderProjection,
1239
1244
  scale: String(document2.system?.properties.scale ?? layoutPreset),
1240
1245
  units: String(document2.system?.properties.units ?? "mixed"),
1241
- preset: frame.preset ?? "custom"
1246
+ preset: frame.preset ?? "custom",
1247
+ ...camera?.azimuth !== null ? { "camera.azimuth": String(camera?.azimuth) } : {},
1248
+ ...camera?.elevation !== null ? { "camera.elevation": String(camera?.elevation) } : {},
1249
+ ...camera?.roll !== null ? { "camera.roll": String(camera?.roll) } : {},
1250
+ ...camera?.distance !== null ? { "camera.distance": String(camera?.distance) } : {}
1242
1251
  },
1243
1252
  contentBounds,
1244
1253
  layers,
@@ -1275,21 +1284,42 @@ var WorldOrbit = (() => {
1275
1284
  return cloned;
1276
1285
  }
1277
1286
  const objectMap = new Map(cloned.map((object) => [object.id, object]));
1287
+ const referencedIds = /* @__PURE__ */ new Set([
1288
+ ...activeEvent.targetObjectId ? [activeEvent.targetObjectId] : [],
1289
+ ...activeEvent.participantObjectIds,
1290
+ ...activeEvent.positions.map((pose) => pose.objectId)
1291
+ ]);
1292
+ for (const objectId of referencedIds) {
1293
+ const object = objectMap.get(objectId);
1294
+ if (!object) {
1295
+ continue;
1296
+ }
1297
+ if (activeEvent.epoch) {
1298
+ object.epoch = activeEvent.epoch;
1299
+ }
1300
+ if (activeEvent.referencePlane) {
1301
+ object.referencePlane = activeEvent.referencePlane;
1302
+ }
1303
+ }
1278
1304
  for (const pose of activeEvent.positions) {
1279
1305
  const object = objectMap.get(pose.objectId);
1280
1306
  if (!object) {
1281
1307
  continue;
1282
1308
  }
1283
- object.placement = pose.placement ? structuredClone(pose.placement) : null;
1309
+ if (pose.placement) {
1310
+ object.placement = structuredClone(pose.placement);
1311
+ }
1284
1312
  if (pose.inner) {
1285
1313
  object.properties.inner = { ...pose.inner };
1286
- } else {
1287
- delete object.properties.inner;
1288
1314
  }
1289
1315
  if (pose.outer) {
1290
1316
  object.properties.outer = { ...pose.outer };
1291
- } else {
1292
- delete object.properties.outer;
1317
+ }
1318
+ if (pose.epoch) {
1319
+ object.epoch = pose.epoch;
1320
+ }
1321
+ if (pose.referencePlane) {
1322
+ object.referencePlane = pose.referencePlane;
1293
1323
  }
1294
1324
  }
1295
1325
  return cloned;
@@ -1330,10 +1360,59 @@ var WorldOrbit = (() => {
1330
1360
  }
1331
1361
  }
1332
1362
  function resolveProjection(document2, projection) {
1333
- if (projection === "topdown" || projection === "isometric") {
1363
+ if (projection === "topdown" || projection === "isometric" || projection === "orthographic" || projection === "perspective") {
1334
1364
  return projection;
1335
1365
  }
1336
- return String(document2.system?.properties.view ?? "topdown").toLowerCase() === "isometric" ? "isometric" : "topdown";
1366
+ const documentView = String(document2.system?.properties.view ?? "topdown").toLowerCase();
1367
+ return parseViewProjection(documentView) ?? "topdown";
1368
+ }
1369
+ function resolveRenderProjection(projection, camera) {
1370
+ switch (projection) {
1371
+ case "topdown":
1372
+ return "topdown";
1373
+ case "isometric":
1374
+ return "isometric";
1375
+ case "orthographic":
1376
+ return camera && (camera.azimuth !== null || camera.elevation !== null || camera.roll !== null) ? "isometric" : "topdown";
1377
+ case "perspective":
1378
+ return "isometric";
1379
+ }
1380
+ }
1381
+ function normalizeViewCamera(camera) {
1382
+ if (!camera) {
1383
+ return null;
1384
+ }
1385
+ const normalized = {
1386
+ azimuth: normalizeFiniteCameraValue(camera.azimuth),
1387
+ elevation: normalizeFiniteCameraValue(camera.elevation),
1388
+ roll: normalizeFiniteCameraValue(camera.roll),
1389
+ distance: normalizePositiveCameraDistance(camera.distance)
1390
+ };
1391
+ return normalized.azimuth !== null || normalized.elevation !== null || normalized.roll !== null || normalized.distance !== null ? normalized : null;
1392
+ }
1393
+ function normalizeFiniteCameraValue(value) {
1394
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1395
+ }
1396
+ function normalizePositiveCameraDistance(value) {
1397
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
1398
+ }
1399
+ function buildSceneSubtitle(projection, renderProjection, layoutPreset, camera) {
1400
+ const parts = [`${capitalizeLabel(projection)} view`, `${capitalizeLabel(layoutPreset)} layout`];
1401
+ if (projection !== renderProjection) {
1402
+ parts.push(`2D ${renderProjection} fallback`);
1403
+ }
1404
+ if (camera) {
1405
+ const cameraParts = [
1406
+ camera.azimuth !== null ? `az ${camera.azimuth}` : null,
1407
+ camera.elevation !== null ? `el ${camera.elevation}` : null,
1408
+ camera.roll !== null ? `roll ${camera.roll}` : null,
1409
+ camera.distance !== null ? `dist ${camera.distance}` : null
1410
+ ].filter(Boolean);
1411
+ if (cameraParts.length > 0) {
1412
+ parts.push(`camera ${cameraParts.join(" / ")}`);
1413
+ }
1414
+ }
1415
+ return parts.join(" - ");
1337
1416
  }
1338
1417
  function resolveScaleModel(layoutPreset, overrides) {
1339
1418
  const defaults = defaultScaleModel(layoutPreset);
@@ -1769,6 +1848,8 @@ var WorldOrbit = (() => {
1769
1848
  function createGeneratedOverviewViewpoint(document2, projection, preset) {
1770
1849
  const title = document2.system?.title ?? document2.system?.properties.title;
1771
1850
  const label = title ? `${String(title)} Overview` : "Overview";
1851
+ const camera = normalizeViewCamera(null);
1852
+ const renderProjection = resolveRenderProjection(projection, camera);
1772
1853
  return {
1773
1854
  id: "overview",
1774
1855
  label,
@@ -1777,6 +1858,8 @@ var WorldOrbit = (() => {
1777
1858
  selectedObjectId: null,
1778
1859
  eventIds: [],
1779
1860
  projection,
1861
+ renderProjection,
1862
+ camera,
1780
1863
  preset,
1781
1864
  rotationDeg: 0,
1782
1865
  scale: null,
@@ -1826,6 +1909,30 @@ var WorldOrbit = (() => {
1826
1909
  case "angle":
1827
1910
  draft.rotationDeg = parseFiniteNumber(normalizedValue) ?? draft.rotationDeg ?? 0;
1828
1911
  return;
1912
+ case "camera.azimuth":
1913
+ draft.camera = {
1914
+ ...draft.camera ?? createEmptyViewCamera(),
1915
+ azimuth: parseFiniteNumber(normalizedValue)
1916
+ };
1917
+ return;
1918
+ case "camera.elevation":
1919
+ draft.camera = {
1920
+ ...draft.camera ?? createEmptyViewCamera(),
1921
+ elevation: parseFiniteNumber(normalizedValue)
1922
+ };
1923
+ return;
1924
+ case "camera.roll":
1925
+ draft.camera = {
1926
+ ...draft.camera ?? createEmptyViewCamera(),
1927
+ roll: parseFiniteNumber(normalizedValue)
1928
+ };
1929
+ return;
1930
+ case "camera.distance":
1931
+ draft.camera = {
1932
+ ...draft.camera ?? createEmptyViewCamera(),
1933
+ distance: parsePositiveNumber(normalizedValue)
1934
+ };
1935
+ return;
1829
1936
  case "zoom":
1830
1937
  case "scale":
1831
1938
  draft.scale = parsePositiveNumber(normalizedValue);
@@ -1865,6 +1972,9 @@ var WorldOrbit = (() => {
1865
1972
  const selectedObjectId = draft.select && objectMap.has(draft.select) ? draft.select : objectId;
1866
1973
  const filter = normalizeViewpointFilter(draft.filter);
1867
1974
  const label = draft.label?.trim() || humanizeIdentifier(draft.id);
1975
+ const resolvedProjection = draft.projection ?? projection;
1976
+ const camera = normalizeViewCamera(draft.camera ?? null);
1977
+ const renderProjection = resolveRenderProjection(resolvedProjection, camera);
1868
1978
  return {
1869
1979
  id: draft.id,
1870
1980
  label,
@@ -1872,7 +1982,9 @@ var WorldOrbit = (() => {
1872
1982
  objectId,
1873
1983
  selectedObjectId,
1874
1984
  eventIds: [...new Set(draft.eventIds ?? [])],
1875
- projection: draft.projection ?? projection,
1985
+ projection: resolvedProjection,
1986
+ renderProjection,
1987
+ camera,
1876
1988
  preset: draft.preset ?? preset,
1877
1989
  rotationDeg: draft.rotationDeg ?? 0,
1878
1990
  scale: draft.scale ?? null,
@@ -1889,6 +2001,14 @@ var WorldOrbit = (() => {
1889
2001
  groupIds: []
1890
2002
  };
1891
2003
  }
2004
+ function createEmptyViewCamera() {
2005
+ return {
2006
+ azimuth: null,
2007
+ elevation: null,
2008
+ roll: null,
2009
+ distance: null
2010
+ };
2011
+ }
1892
2012
  function normalizeViewpointFilter(filter) {
1893
2013
  if (!filter) {
1894
2014
  return null;
@@ -1902,7 +2022,18 @@ var WorldOrbit = (() => {
1902
2022
  return normalized.query || normalized.objectTypes.length > 0 || normalized.tags.length > 0 || normalized.groupIds.length > 0 ? normalized : null;
1903
2023
  }
1904
2024
  function parseViewProjection(value) {
1905
- return value.toLowerCase() === "isometric" ? "isometric" : value.toLowerCase() === "topdown" ? "topdown" : null;
2025
+ switch (value.toLowerCase()) {
2026
+ case "topdown":
2027
+ return "topdown";
2028
+ case "isometric":
2029
+ return "isometric";
2030
+ case "orthographic":
2031
+ return "orthographic";
2032
+ case "perspective":
2033
+ return "perspective";
2034
+ default:
2035
+ return null;
2036
+ }
1906
2037
  }
1907
2038
  function parseRenderPreset(value) {
1908
2039
  const normalized = value.toLowerCase();
@@ -1940,7 +2071,7 @@ var WorldOrbit = (() => {
1940
2071
  }
1941
2072
  function parseViewpointGroups(value, document2, relationships, objectMap) {
1942
2073
  return splitListValue(value).map((entry) => {
1943
- if (document2.schemaVersion === "2.1" || document2.groups.some((group) => group.id === entry)) {
2074
+ if (document2.schemaVersion === "2.1" || document2.schemaVersion === "2.5" || document2.groups.some((group) => group.id === entry)) {
1944
2075
  return entry;
1945
2076
  }
1946
2077
  if (entry.startsWith("wo-") && entry.endsWith("-group")) {
@@ -2733,8 +2864,8 @@ var WorldOrbit = (() => {
2733
2864
  }
2734
2865
  return {
2735
2866
  format: "worldorbit",
2736
- version: "2.0",
2737
- schemaVersion: "2.0",
2867
+ version: "2.5",
2868
+ schemaVersion: "2.5",
2738
2869
  sourceVersion: document2.version,
2739
2870
  system,
2740
2871
  groups: structuredClone(document2.groups ?? []),
@@ -2793,8 +2924,9 @@ var WorldOrbit = (() => {
2793
2924
  };
2794
2925
  }
2795
2926
  function createDraftDefaults(document2, preset, projection) {
2927
+ const rawView = typeof document2.system?.properties.view === "string" ? document2.system.properties.view.toLowerCase() : null;
2796
2928
  return {
2797
- view: typeof document2.system?.properties.view === "string" && document2.system.properties.view.toLowerCase() === "topdown" ? "topdown" : projection,
2929
+ view: rawView === "topdown" || rawView === "isometric" || rawView === "orthographic" || rawView === "perspective" ? rawView : projection,
2798
2930
  scale: typeof document2.system?.properties.scale === "string" ? document2.system.properties.scale : null,
2799
2931
  units: typeof document2.system?.properties.units === "string" ? document2.system.properties.units : null,
2800
2932
  preset,
@@ -2901,6 +3033,7 @@ var WorldOrbit = (() => {
2901
3033
  preset: viewpoint.preset,
2902
3034
  zoom: viewpoint.scale,
2903
3035
  rotationDeg: viewpoint.rotationDeg,
3036
+ camera: viewpoint.camera ? { ...viewpoint.camera } : null,
2904
3037
  layers: { ...viewpoint.layers },
2905
3038
  filter: viewpoint.filter ? {
2906
3039
  query: viewpoint.filter.query,
@@ -2942,7 +3075,9 @@ var WorldOrbit = (() => {
2942
3075
  objectId: pose.objectId,
2943
3076
  placement: clonePlacement(pose.placement),
2944
3077
  inner: pose.inner ? { ...pose.inner } : void 0,
2945
- outer: pose.outer ? { ...pose.outer } : void 0
3078
+ outer: pose.outer ? { ...pose.outer } : void 0,
3079
+ epoch: pose.epoch ?? null,
3080
+ referencePlane: pose.referencePlane ?? null
2946
3081
  };
2947
3082
  }
2948
3083
  function clonePlacement(placement) {
@@ -2957,21 +3092,42 @@ var WorldOrbit = (() => {
2957
3092
  return;
2958
3093
  }
2959
3094
  const objectMap = new Map(objects.map((object) => [object.id, object]));
3095
+ const referencedIds = /* @__PURE__ */ new Set([
3096
+ ...event.targetObjectId ? [event.targetObjectId] : [],
3097
+ ...event.participantObjectIds,
3098
+ ...event.positions.map((pose) => pose.objectId)
3099
+ ]);
3100
+ for (const objectId of referencedIds) {
3101
+ const object = objectMap.get(objectId);
3102
+ if (!object) {
3103
+ continue;
3104
+ }
3105
+ if (event.epoch) {
3106
+ object.epoch = event.epoch;
3107
+ }
3108
+ if (event.referencePlane) {
3109
+ object.referencePlane = event.referencePlane;
3110
+ }
3111
+ }
2960
3112
  for (const pose of event.positions) {
2961
3113
  const object = objectMap.get(pose.objectId);
2962
3114
  if (!object) {
2963
3115
  continue;
2964
3116
  }
2965
- object.placement = clonePlacement(pose.placement);
3117
+ if (pose.placement) {
3118
+ object.placement = clonePlacement(pose.placement);
3119
+ }
2966
3120
  if (pose.inner) {
2967
3121
  object.properties.inner = { ...pose.inner };
2968
- } else {
2969
- delete object.properties.inner;
2970
3122
  }
2971
3123
  if (pose.outer) {
2972
3124
  object.properties.outer = { ...pose.outer };
2973
- } else {
2974
- delete object.properties.outer;
3125
+ }
3126
+ if (pose.epoch) {
3127
+ object.epoch = pose.epoch;
3128
+ }
3129
+ if (pose.referencePlane) {
3130
+ object.referencePlane = pose.referencePlane;
2975
3131
  }
2976
3132
  }
2977
3133
  }
@@ -3056,6 +3212,18 @@ var WorldOrbit = (() => {
3056
3212
  if (viewpoint.rotationDeg !== 0) {
3057
3213
  info2[`${prefix}.rotation`] = String(viewpoint.rotationDeg);
3058
3214
  }
3215
+ if (viewpoint.camera?.azimuth !== null) {
3216
+ info2[`${prefix}.camera.azimuth`] = String(viewpoint.camera?.azimuth);
3217
+ }
3218
+ if (viewpoint.camera?.elevation !== null) {
3219
+ info2[`${prefix}.camera.elevation`] = String(viewpoint.camera?.elevation);
3220
+ }
3221
+ if (viewpoint.camera?.roll !== null) {
3222
+ info2[`${prefix}.camera.roll`] = String(viewpoint.camera?.roll);
3223
+ }
3224
+ if (viewpoint.camera?.distance !== null) {
3225
+ info2[`${prefix}.camera.distance`] = String(viewpoint.camera?.distance);
3226
+ }
3059
3227
  const serializedLayers = serializeViewpointLayers(viewpoint.layers);
3060
3228
  if (serializedLayers) {
3061
3229
  info2[`${prefix}.layers`] = serializedLayers;
@@ -3152,26 +3320,26 @@ var WorldOrbit = (() => {
3152
3320
  ];
3153
3321
  function formatDocument(document2, options = {}) {
3154
3322
  const schema = options.schema ?? "auto";
3155
- const useDraft = schema === "2.0" || schema === "2.1" || schema === "2.0-draft" || document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.0-draft";
3323
+ const useDraft = schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.0-draft" || document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" || document2.version === "2.0-draft";
3156
3324
  if (useDraft) {
3157
3325
  if (schema === "2.0-draft") {
3158
- const legacyDraftDocument = document2.version === "2.0-draft" ? document2 : document2.version === "2.0" || document2.version === "2.1" ? {
3326
+ const legacyDraftDocument = document2.version === "2.0-draft" ? document2 : document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" ? {
3159
3327
  ...document2,
3160
3328
  version: "2.0-draft",
3161
3329
  schemaVersion: "2.0-draft"
3162
3330
  } : upgradeDocumentToDraftV2(document2);
3163
3331
  return formatDraftDocument(legacyDraftDocument);
3164
3332
  }
3165
- const atlasDocument = document2.version === "2.0" || document2.version === "2.1" ? document2 : document2.version === "2.0-draft" ? {
3333
+ const atlasDocument = document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" ? document2 : document2.version === "2.0-draft" ? {
3166
3334
  ...document2,
3167
3335
  version: "2.0",
3168
3336
  schemaVersion: "2.0"
3169
3337
  } : upgradeDocumentToV2(document2);
3170
- if (schema === "2.1" && atlasDocument.version !== "2.1") {
3338
+ if ((schema === "2.0" || schema === "2.1" || schema === "2.5") && atlasDocument.version !== schema) {
3171
3339
  return formatAtlasDocument({
3172
3340
  ...atlasDocument,
3173
- version: "2.1",
3174
- schemaVersion: "2.1"
3341
+ version: schema,
3342
+ schemaVersion: schema
3175
3343
  });
3176
3344
  }
3177
3345
  return formatAtlasDocument(atlasDocument);
@@ -3448,6 +3616,21 @@ var WorldOrbit = (() => {
3448
3616
  if (viewpoint.rotationDeg !== 0) {
3449
3617
  lines.push(` rotation ${viewpoint.rotationDeg}`);
3450
3618
  }
3619
+ if (viewpoint.camera && hasCameraValues(viewpoint.camera)) {
3620
+ lines.push(" camera");
3621
+ if (viewpoint.camera.azimuth !== null) {
3622
+ lines.push(` azimuth ${viewpoint.camera.azimuth}`);
3623
+ }
3624
+ if (viewpoint.camera.elevation !== null) {
3625
+ lines.push(` elevation ${viewpoint.camera.elevation}`);
3626
+ }
3627
+ if (viewpoint.camera.roll !== null) {
3628
+ lines.push(` roll ${viewpoint.camera.roll}`);
3629
+ }
3630
+ if (viewpoint.camera.distance !== null) {
3631
+ lines.push(` distance ${viewpoint.camera.distance}`);
3632
+ }
3633
+ }
3451
3634
  const layerTokens = formatDraftLayers(viewpoint.layers);
3452
3635
  if (layerTokens.length > 0) {
3453
3636
  lines.push(` layers ${layerTokens.join(" ")}`);
@@ -3547,6 +3730,12 @@ var WorldOrbit = (() => {
3547
3730
  if (event.visibility) {
3548
3731
  lines.push(` visibility ${quoteIfNeeded(event.visibility)}`);
3549
3732
  }
3733
+ if (event.epoch) {
3734
+ lines.push(` epoch ${quoteIfNeeded(event.epoch)}`);
3735
+ }
3736
+ if (event.referencePlane) {
3737
+ lines.push(` referencePlane ${quoteIfNeeded(event.referencePlane)}`);
3738
+ }
3550
3739
  if (event.tags.length > 0) {
3551
3740
  lines.push(` tags ${event.tags.map(quoteIfNeeded).join(" ")}`);
3552
3741
  }
@@ -3571,10 +3760,15 @@ var WorldOrbit = (() => {
3571
3760
  function formatEventPoseFields(pose) {
3572
3761
  return [
3573
3762
  ...formatPlacement(pose.placement),
3763
+ ...pose.epoch ? [`epoch ${quoteIfNeeded(pose.epoch)}`] : [],
3764
+ ...pose.referencePlane ? [`referencePlane ${quoteIfNeeded(pose.referencePlane)}`] : [],
3574
3765
  ...formatOptionalUnit("inner", pose.inner),
3575
3766
  ...formatOptionalUnit("outer", pose.outer)
3576
3767
  ];
3577
3768
  }
3769
+ function hasCameraValues(camera) {
3770
+ return camera.azimuth !== null || camera.elevation !== null || camera.roll !== null || camera.distance !== null;
3771
+ }
3578
3772
  function formatValue(value) {
3579
3773
  if (Array.isArray(value)) {
3580
3774
  return value.map((item) => quoteIfNeeded(item)).join(" ");
@@ -3868,13 +4062,13 @@ var WorldOrbit = (() => {
3868
4062
  validateRelation(relation, objectMap, diagnostics);
3869
4063
  }
3870
4064
  for (const viewpoint of document2.system?.viewpoints ?? []) {
3871
- validateViewpoint(viewpoint.filter, viewpoint.events ?? [], groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpoint.id);
4065
+ validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap);
3872
4066
  }
3873
4067
  for (const object of document2.objects) {
3874
4068
  validateObject(object, document2.system, objectMap, groupIds, diagnostics);
3875
4069
  }
3876
4070
  for (const event of document2.events) {
3877
- validateEvent(event, objectMap, diagnostics);
4071
+ validateEvent(event, document2.system, objectMap, diagnostics);
3878
4072
  }
3879
4073
  return diagnostics;
3880
4074
  }
@@ -3893,21 +4087,24 @@ var WorldOrbit = (() => {
3893
4087
  diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
3894
4088
  }
3895
4089
  }
3896
- function validateViewpoint(filter, eventRefs, groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpointId) {
3897
- if (sourceSchemaVersion === "2.1") {
4090
+ function validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap) {
4091
+ const filter = viewpoint.filter;
4092
+ if (sourceSchemaVersion === "2.1" || sourceSchemaVersion === "2.5") {
3898
4093
  if (filter) {
3899
4094
  for (const groupId of filter.groupIds) {
3900
4095
  if (!groupIds.has(groupId)) {
3901
- diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.groups`));
4096
+ diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.groups`));
3902
4097
  }
3903
4098
  }
3904
4099
  }
3905
- for (const eventId of eventRefs) {
4100
+ for (const eventId of viewpoint.events ?? []) {
3906
4101
  if (!eventIds.has(eventId)) {
3907
- diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.events`));
4102
+ diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.events`));
3908
4103
  }
3909
4104
  }
3910
4105
  }
4106
+ validateProjection(viewpoint.projection, diagnostics, `viewpoint.${viewpoint.id}.projection`, viewpoint.id);
4107
+ validateCamera(viewpoint.camera, viewpoint.projection, viewpoint.rotationDeg, diagnostics, viewpoint.id, viewpoint.focusObjectId, viewpoint.selectedObjectId, filter, objectMap);
3911
4108
  }
3912
4109
  function validateObject(object, system, objectMap, groupIds, diagnostics) {
3913
4110
  const placement = object.placement;
@@ -3920,6 +4117,12 @@ var WorldOrbit = (() => {
3920
4117
  }
3921
4118
  }
3922
4119
  }
4120
+ if (typeof object.epoch === "string" && !object.epoch.trim()) {
4121
+ diagnostics.push(warn("validate.epoch.empty", `Object "${object.id}" defines an empty epoch string.`, object.id, "epoch"));
4122
+ }
4123
+ if (typeof object.referencePlane === "string" && !object.referencePlane.trim()) {
4124
+ diagnostics.push(warn("validate.referencePlane.empty", `Object "${object.id}" defines an empty reference plane string.`, object.id, "referencePlane"));
4125
+ }
3923
4126
  if (orbitPlacement) {
3924
4127
  if (!objectMap.has(orbitPlacement.target)) {
3925
4128
  diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
@@ -3991,12 +4194,18 @@ var WorldOrbit = (() => {
3991
4194
  }
3992
4195
  }
3993
4196
  }
3994
- function validateEvent(event, objectMap, diagnostics) {
4197
+ function validateEvent(event, system, objectMap, diagnostics) {
3995
4198
  const fieldPrefix = `event.${event.id}`;
3996
4199
  const referencedIds = /* @__PURE__ */ new Set();
3997
4200
  if (!event.kind.trim()) {
3998
4201
  diagnostics.push(error("validate.event.kind.required", `Event "${event.id}" is missing a "kind" value.`, void 0, `${fieldPrefix}.kind`));
3999
4202
  }
4203
+ if (typeof event.epoch === "string" && !event.epoch.trim()) {
4204
+ diagnostics.push(warn("validate.event.epoch.empty", `Event "${event.id}" defines an empty epoch string.`, void 0, `${fieldPrefix}.epoch`));
4205
+ }
4206
+ if (typeof event.referencePlane === "string" && !event.referencePlane.trim()) {
4207
+ diagnostics.push(warn("validate.event.referencePlane.empty", `Event "${event.id}" defines an empty reference plane string.`, void 0, `${fieldPrefix}.referencePlane`));
4208
+ }
4000
4209
  if (!event.targetObjectId && event.participantObjectIds.length === 0) {
4001
4210
  diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
4002
4211
  }
@@ -4043,10 +4252,14 @@ var WorldOrbit = (() => {
4043
4252
  if (!referencedIds.has(pose.objectId)) {
4044
4253
  diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
4045
4254
  }
4046
- validateEventPose(pose, object, objectMap, diagnostics, poseFieldPrefix, event.id);
4255
+ validateEventPose(pose, object, event, system, objectMap, diagnostics, poseFieldPrefix, event.id);
4256
+ }
4257
+ const missingPoseIds = [...referencedIds].filter((objectId) => !poseIds.has(objectId));
4258
+ if (event.positions.length > 0 && missingPoseIds.length > 0) {
4259
+ diagnostics.push(warn("validate.event.positions.partial", `Event "${event.id}" leaves ${missingPoseIds.length} referenced object(s) on their base placement.`, void 0, `${fieldPrefix}.positions`));
4047
4260
  }
4048
4261
  }
4049
- function validateEventPose(pose, object, objectMap, diagnostics, fieldPrefix, eventId) {
4262
+ function validateEventPose(pose, object, event, system, objectMap, diagnostics, fieldPrefix, eventId) {
4050
4263
  const placement = pose.placement;
4051
4264
  if (!placement) {
4052
4265
  diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
@@ -4059,6 +4272,15 @@ var WorldOrbit = (() => {
4059
4272
  if (placement.distance && placement.semiMajor) {
4060
4273
  diagnostics.push(error("validate.event.pose.orbit.distanceConflict", `Event "${eventId}" pose "${pose.objectId}" cannot declare both "distance" and "semiMajor".`, void 0, `${fieldPrefix}.distance`));
4061
4274
  }
4275
+ if (placement.phase && !resolveEffectiveEpoch(system, object, event, pose)) {
4276
+ diagnostics.push(warn("validate.event.pose.phase.epochMissing", `Event "${eventId}" pose "${pose.objectId}" sets "phase" without an effective epoch.`, void 0, `${fieldPrefix}.phase`));
4277
+ }
4278
+ if (placement.inclination && !resolveEffectiveReferencePlane(system, object, event, pose)) {
4279
+ diagnostics.push(warn("validate.event.pose.inclination.referencePlaneMissing", `Event "${eventId}" pose "${pose.objectId}" sets "inclination" without an effective reference plane.`, void 0, `${fieldPrefix}.inclination`));
4280
+ }
4281
+ if (placement.period && !massInSolar(objectMap.get(placement.target)?.properties.mass)) {
4282
+ 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`));
4283
+ }
4062
4284
  return;
4063
4285
  }
4064
4286
  if (placement.mode === "surface") {
@@ -4193,6 +4415,52 @@ var WorldOrbit = (() => {
4193
4415
  return null;
4194
4416
  }
4195
4417
  }
4418
+ function validateProjection(projection, diagnostics, field, viewpointId) {
4419
+ if (projection !== "topdown" && projection !== "isometric" && projection !== "orthographic" && projection !== "perspective") {
4420
+ diagnostics.push(error("validate.viewpoint.projection.invalid", `Unknown projection "${String(projection)}" in viewpoint "${viewpointId}".`, void 0, field));
4421
+ }
4422
+ }
4423
+ function validateCamera(camera, projection, rotationDeg, diagnostics, viewpointId, focusObjectId, selectedObjectId, filter, objectMap) {
4424
+ if (!camera) {
4425
+ return;
4426
+ }
4427
+ const prefix = `viewpoint.${viewpointId}.camera`;
4428
+ for (const [key, value] of [
4429
+ ["azimuth", camera.azimuth],
4430
+ ["elevation", camera.elevation],
4431
+ ["roll", camera.roll],
4432
+ ["distance", camera.distance]
4433
+ ]) {
4434
+ if (value !== null && (!Number.isFinite(value) || key === "distance" && value <= 0)) {
4435
+ diagnostics.push(error("validate.viewpoint.camera.invalid", `Invalid camera ${key} "${String(value)}" in viewpoint "${viewpointId}".`, void 0, `${prefix}.${key}`));
4436
+ }
4437
+ }
4438
+ if (camera.distance !== null && projection !== "perspective") {
4439
+ 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`));
4440
+ }
4441
+ if (projection === "topdown" && (camera.elevation !== null || camera.roll !== null)) {
4442
+ 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));
4443
+ }
4444
+ if (projection === "isometric" && camera.elevation !== null) {
4445
+ diagnostics.push(info("validate.viewpoint.camera.isometricStored", `Camera elevation on isometric viewpoint "${viewpointId}" is preserved semantically for future 3D rendering.`, void 0, `${prefix}.elevation`));
4446
+ }
4447
+ if (camera.azimuth !== null && camera.azimuth !== 0 && rotationDeg !== 0) {
4448
+ 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`));
4449
+ }
4450
+ const hasAnchor = focusObjectId !== null && objectMap.has(focusObjectId) || selectedObjectId !== null && objectMap.has(selectedObjectId) || !!filter;
4451
+ if (!hasAnchor) {
4452
+ diagnostics.push(info("validate.viewpoint.camera.anchorMissing", `Viewpoint "${viewpointId}" stores camera settings without a focus object, selection, or filter anchor.`, void 0, prefix));
4453
+ }
4454
+ }
4455
+ function resolveEffectiveEpoch(system, object, event, pose) {
4456
+ return normalizeOptionalContextString(pose?.epoch) ?? normalizeOptionalContextString(event?.epoch) ?? normalizeOptionalContextString(object.epoch) ?? normalizeOptionalContextString(system?.epoch) ?? null;
4457
+ }
4458
+ function resolveEffectiveReferencePlane(system, object, event, pose) {
4459
+ return normalizeOptionalContextString(pose?.referencePlane) ?? normalizeOptionalContextString(event?.referencePlane) ?? normalizeOptionalContextString(object.referencePlane) ?? normalizeOptionalContextString(system?.referencePlane) ?? null;
4460
+ }
4461
+ function normalizeOptionalContextString(value) {
4462
+ return typeof value === "string" && value.trim() ? value.trim() : null;
4463
+ }
4196
4464
  function toleranceForField(object, field) {
4197
4465
  const tolerance = object.tolerances?.find((entry) => entry.field === field)?.value;
4198
4466
  if (typeof tolerance === "number") {
@@ -4301,7 +4569,9 @@ var WorldOrbit = (() => {
4301
4569
  "surface",
4302
4570
  "free",
4303
4571
  "inner",
4304
- "outer"
4572
+ "outer",
4573
+ "epoch",
4574
+ "referencePlane"
4305
4575
  ]);
4306
4576
  function parseWorldOrbitAtlas(source) {
4307
4577
  return parseAtlasSource(source);
@@ -4346,7 +4616,7 @@ var WorldOrbit = (() => {
4346
4616
  if (!sawSchemaHeader) {
4347
4617
  sourceSchemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
4348
4618
  sawSchemaHeader = true;
4349
- if (prepared.comments.length > 0 && sourceSchemaVersion !== "2.1") {
4619
+ if (prepared.comments.length > 0 && isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
4350
4620
  diagnostics.push({
4351
4621
  code: "parse.schema21.commentCompatibility",
4352
4622
  severity: "warning",
@@ -4416,11 +4686,11 @@ var WorldOrbit = (() => {
4416
4686
  return document2;
4417
4687
  }
4418
4688
  function assertDraftSchemaHeader(tokens, line) {
4419
- if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1"].includes(tokens[1].value.toLowerCase())) {
4420
- throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
4689
+ if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1", "2.5"].includes(tokens[1].value.toLowerCase())) {
4690
+ 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);
4421
4691
  }
4422
4692
  const version = tokens[1].value.toLowerCase();
4423
- return version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
4693
+ return version === "2.5" ? "2.5" : version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
4424
4694
  }
4425
4695
  function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
4426
4696
  const keyword = tokens[0]?.value.toLowerCase();
@@ -4440,6 +4710,8 @@ var WorldOrbit = (() => {
4440
4710
  return {
4441
4711
  kind: "defaults",
4442
4712
  system,
4713
+ sourceSchemaVersion,
4714
+ diagnostics,
4443
4715
  seenFields: /* @__PURE__ */ new Set()
4444
4716
  };
4445
4717
  case "atlas":
@@ -4532,6 +4804,7 @@ var WorldOrbit = (() => {
4532
4804
  preset: system.defaults.preset,
4533
4805
  zoom: null,
4534
4806
  rotationDeg: 0,
4807
+ camera: null,
4535
4808
  layers: {},
4536
4809
  filter: null
4537
4810
  };
@@ -4545,7 +4818,10 @@ var WorldOrbit = (() => {
4545
4818
  seenFields: /* @__PURE__ */ new Set(),
4546
4819
  inFilter: false,
4547
4820
  filterIndent: null,
4548
- seenFilterFields: /* @__PURE__ */ new Set()
4821
+ seenFilterFields: /* @__PURE__ */ new Set(),
4822
+ inCamera: false,
4823
+ cameraIndent: null,
4824
+ seenCameraFields: /* @__PURE__ */ new Set()
4549
4825
  };
4550
4826
  }
4551
4827
  function startAnnotationSection(tokens, line, system, annotationIds) {
@@ -4652,6 +4928,8 @@ var WorldOrbit = (() => {
4652
4928
  participantObjectIds: [],
4653
4929
  timing: null,
4654
4930
  visibility: null,
4931
+ epoch: null,
4932
+ referencePlane: null,
4655
4933
  tags: [],
4656
4934
  color: null,
4657
4935
  hidden: false,
@@ -4776,6 +5054,12 @@ var WorldOrbit = (() => {
4776
5054
  const value = joinFieldValue(tokens, line);
4777
5055
  switch (key) {
4778
5056
  case "view":
5057
+ if (isSchema25Projection(value)) {
5058
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "defaults.view", {
5059
+ line,
5060
+ column: tokens[0].column
5061
+ });
5062
+ }
4779
5063
  section.system.defaults.view = parseProjectionValue(value, line, tokens[0].column);
4780
5064
  return;
4781
5065
  case "scale":
@@ -4815,14 +5099,36 @@ var WorldOrbit = (() => {
4815
5099
  throw new WorldOrbitError(`Unknown atlas field "${tokens[0].value}"`, line, tokens[0].column);
4816
5100
  }
4817
5101
  function applyViewpointField2(section, indent, tokens, line) {
5102
+ if (section.inCamera && indent <= (section.cameraIndent ?? 0)) {
5103
+ section.inCamera = false;
5104
+ section.cameraIndent = null;
5105
+ }
4818
5106
  if (section.inFilter && indent <= (section.filterIndent ?? 0)) {
4819
5107
  section.inFilter = false;
4820
5108
  section.filterIndent = null;
4821
5109
  }
5110
+ if (section.inCamera) {
5111
+ applyViewpointCameraField(section, tokens, line);
5112
+ return;
5113
+ }
4822
5114
  if (section.inFilter) {
4823
5115
  applyViewpointFilterField(section, tokens, line);
4824
5116
  return;
4825
5117
  }
5118
+ if (tokens.length === 1 && tokens[0].value.toLowerCase() === "camera") {
5119
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
5120
+ line,
5121
+ column: tokens[0].column
5122
+ });
5123
+ if (section.seenFields.has("camera")) {
5124
+ throw new WorldOrbitError('Duplicate viewpoint field "camera"', line, tokens[0].column);
5125
+ }
5126
+ section.seenFields.add("camera");
5127
+ section.inCamera = true;
5128
+ section.cameraIndent = indent;
5129
+ section.viewpoint.camera = section.viewpoint.camera ?? createEmptyViewCamera2();
5130
+ return;
5131
+ }
4826
5132
  if (tokens.length === 1 && tokens[0].value.toLowerCase() === "filter") {
4827
5133
  if (section.seenFields.has("filter")) {
4828
5134
  throw new WorldOrbitError('Duplicate viewpoint field "filter"', line, tokens[0].column);
@@ -4848,6 +5154,12 @@ var WorldOrbit = (() => {
4848
5154
  section.viewpoint.selectedObjectId = value;
4849
5155
  return;
4850
5156
  case "projection":
5157
+ if (isSchema25Projection(value)) {
5158
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "projection", {
5159
+ line,
5160
+ column: tokens[0].column
5161
+ });
5162
+ }
4851
5163
  section.viewpoint.projection = parseProjectionValue(value, line, tokens[0].column);
4852
5164
  return;
4853
5165
  case "preset":
@@ -4859,6 +5171,13 @@ var WorldOrbit = (() => {
4859
5171
  case "rotation":
4860
5172
  section.viewpoint.rotationDeg = parseFiniteNumber2(value, line, tokens[0].column, "rotation");
4861
5173
  return;
5174
+ case "camera":
5175
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
5176
+ line,
5177
+ column: tokens[0].column
5178
+ });
5179
+ section.viewpoint.camera = parseInlineViewCamera(tokens.slice(1), line, section.viewpoint.camera);
5180
+ return;
4862
5181
  case "layers":
4863
5182
  section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line, section.sourceSchemaVersion, section.diagnostics);
4864
5183
  return;
@@ -4873,6 +5192,28 @@ var WorldOrbit = (() => {
4873
5192
  throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
4874
5193
  }
4875
5194
  }
5195
+ function applyViewpointCameraField(section, tokens, line) {
5196
+ const key = requireUniqueField(tokens, section.seenCameraFields, line);
5197
+ const value = joinFieldValue(tokens, line);
5198
+ const camera = section.viewpoint.camera ?? createEmptyViewCamera2();
5199
+ switch (key) {
5200
+ case "azimuth":
5201
+ camera.azimuth = parseFiniteNumber2(value, line, tokens[0].column, "camera.azimuth");
5202
+ break;
5203
+ case "elevation":
5204
+ camera.elevation = parseFiniteNumber2(value, line, tokens[0].column, "camera.elevation");
5205
+ break;
5206
+ case "roll":
5207
+ camera.roll = parseFiniteNumber2(value, line, tokens[0].column, "camera.roll");
5208
+ break;
5209
+ case "distance":
5210
+ camera.distance = parsePositiveNumber2(value, line, tokens[0].column, "camera.distance");
5211
+ break;
5212
+ default:
5213
+ throw new WorldOrbitError(`Unknown viewpoint camera field "${tokens[0].value}"`, line, tokens[0].column);
5214
+ }
5215
+ section.viewpoint.camera = camera;
5216
+ }
4876
5217
  function applyViewpointFilterField(section, tokens, line) {
4877
5218
  const key = requireUniqueField(tokens, section.seenFilterFields, line);
4878
5219
  const filter = section.viewpoint.filter ?? createEmptyViewpointFilter2();
@@ -4983,6 +5324,12 @@ var WorldOrbit = (() => {
4983
5324
  section.positionsIndent = null;
4984
5325
  }
4985
5326
  if (section.activePose) {
5327
+ if (tokens[0]?.value === "epoch" || tokens[0]?.value === "referencePlane") {
5328
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, `pose.${tokens[0].value}`, {
5329
+ line,
5330
+ column: tokens[0]?.column ?? 1
5331
+ });
5332
+ }
4986
5333
  section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
4987
5334
  return;
4988
5335
  }
@@ -5037,6 +5384,20 @@ var WorldOrbit = (() => {
5037
5384
  case "visibility":
5038
5385
  section.event.visibility = joinFieldValue(tokens, line);
5039
5386
  return;
5387
+ case "epoch":
5388
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.epoch", {
5389
+ line,
5390
+ column: tokens[0].column
5391
+ });
5392
+ section.event.epoch = joinFieldValue(tokens, line);
5393
+ return;
5394
+ case "referenceplane":
5395
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.referencePlane", {
5396
+ line,
5397
+ column: tokens[0].column
5398
+ });
5399
+ section.event.referencePlane = joinFieldValue(tokens, line);
5400
+ return;
5040
5401
  case "tags":
5041
5402
  section.event.tags = parseTokenList(tokens.slice(1), line, "tags");
5042
5403
  return;
@@ -5164,11 +5525,15 @@ var WorldOrbit = (() => {
5164
5525
  }
5165
5526
  function parseProjectionValue(value, line, column) {
5166
5527
  const normalized = value.toLowerCase();
5167
- if (normalized !== "topdown" && normalized !== "isometric") {
5528
+ if (normalized !== "topdown" && normalized !== "isometric" && normalized !== "orthographic" && normalized !== "perspective") {
5168
5529
  throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
5169
5530
  }
5170
5531
  return normalized;
5171
5532
  }
5533
+ function isSchema25Projection(value) {
5534
+ const normalized = value.toLowerCase();
5535
+ return normalized === "orthographic" || normalized === "perspective";
5536
+ }
5172
5537
  function parsePresetValue(value, line, column) {
5173
5538
  const normalized = value.toLowerCase();
5174
5539
  if (normalized === "diagram" || normalized === "presentation" || normalized === "atlas-card" || normalized === "markdown") {
@@ -5198,6 +5563,48 @@ var WorldOrbit = (() => {
5198
5563
  groupIds: []
5199
5564
  };
5200
5565
  }
5566
+ function createEmptyViewCamera2() {
5567
+ return {
5568
+ azimuth: null,
5569
+ elevation: null,
5570
+ roll: null,
5571
+ distance: null
5572
+ };
5573
+ }
5574
+ function parseInlineViewCamera(tokens, line, current) {
5575
+ if (tokens.length === 0 || tokens.length % 2 !== 0) {
5576
+ throw new WorldOrbitError('Field "camera" expects "<field> <value>" pairs', line, tokens[0]?.column ?? 1);
5577
+ }
5578
+ const camera = current ? { ...current } : createEmptyViewCamera2();
5579
+ const seen = /* @__PURE__ */ new Set();
5580
+ for (let index = 0; index < tokens.length; index += 2) {
5581
+ const fieldToken = tokens[index];
5582
+ const valueToken = tokens[index + 1];
5583
+ const key = fieldToken.value.toLowerCase();
5584
+ if (seen.has(key)) {
5585
+ throw new WorldOrbitError(`Duplicate viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
5586
+ }
5587
+ seen.add(key);
5588
+ const value = valueToken.value;
5589
+ switch (key) {
5590
+ case "azimuth":
5591
+ camera.azimuth = parseFiniteNumber2(value, line, fieldToken.column, "camera.azimuth");
5592
+ break;
5593
+ case "elevation":
5594
+ camera.elevation = parseFiniteNumber2(value, line, fieldToken.column, "camera.elevation");
5595
+ break;
5596
+ case "roll":
5597
+ camera.roll = parseFiniteNumber2(value, line, fieldToken.column, "camera.roll");
5598
+ break;
5599
+ case "distance":
5600
+ camera.distance = parsePositiveNumber2(value, line, fieldToken.column, "camera.distance");
5601
+ break;
5602
+ default:
5603
+ throw new WorldOrbitError(`Unknown viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
5604
+ }
5605
+ }
5606
+ return camera;
5607
+ }
5201
5608
  function parseInlineObjectFields(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
5202
5609
  const fields = [];
5203
5610
  let index = 0;
@@ -5330,7 +5737,7 @@ var WorldOrbit = (() => {
5330
5737
  object.tolerances = tolerances;
5331
5738
  if (typedBlocks && Object.keys(typedBlocks).length > 0)
5332
5739
  object.typedBlocks = typedBlocks;
5333
- if (sourceSchemaVersion !== "2.1") {
5740
+ if (isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
5334
5741
  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) {
5335
5742
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
5336
5743
  }
@@ -5346,23 +5753,25 @@ var WorldOrbit = (() => {
5346
5753
  };
5347
5754
  }
5348
5755
  function normalizeDraftEventPose(rawPose) {
5349
- const fieldMap = collectDraftFields(rawPose.fields);
5756
+ const fieldMap = collectDraftFields(rawPose.fields, "event-pose");
5350
5757
  const placement = extractPlacementFromFieldMap(fieldMap);
5351
5758
  return {
5352
5759
  objectId: rawPose.objectId,
5353
5760
  placement,
5354
5761
  inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
5355
- outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer")
5762
+ outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer"),
5763
+ epoch: parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]),
5764
+ referencePlane: parseOptionalJoinedValue(fieldMap.get("referencePlane")?.[0])
5356
5765
  };
5357
5766
  }
5358
- function collectDraftFields(fields) {
5767
+ function collectDraftFields(fields, _mode = "object") {
5359
5768
  const grouped = /* @__PURE__ */ new Map();
5360
5769
  for (const field of fields) {
5361
5770
  const spec = getDraftObjectFieldSpec(field.key);
5362
- if (!spec) {
5771
+ if (!spec && !EVENT_POSE_FIELD_KEYS.has(field.key)) {
5363
5772
  throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
5364
5773
  }
5365
- if (!spec.allowRepeat && grouped.has(field.key)) {
5774
+ if (!spec?.allowRepeat && grouped.has(field.key)) {
5366
5775
  throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
5367
5776
  }
5368
5777
  const existing = grouped.get(field.key) ?? [];
@@ -5539,7 +5948,7 @@ var WorldOrbit = (() => {
5539
5948
  }
5540
5949
  }
5541
5950
  function warnIfSchema21Feature(sourceSchemaVersion, diagnostics, featureName, location) {
5542
- if (sourceSchemaVersion === "2.1") {
5951
+ if (!isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
5543
5952
  return;
5544
5953
  }
5545
5954
  diagnostics.push({
@@ -5551,6 +5960,34 @@ var WorldOrbit = (() => {
5551
5960
  column: location.column
5552
5961
  });
5553
5962
  }
5963
+ function warnIfSchema25Feature(sourceSchemaVersion, diagnostics, featureName, location) {
5964
+ if (!isSchemaOlderThan(sourceSchemaVersion, "2.5")) {
5965
+ return;
5966
+ }
5967
+ diagnostics.push({
5968
+ code: "parse.schema25.featureCompatibility",
5969
+ severity: "warning",
5970
+ source: "parse",
5971
+ message: `Feature "${featureName}" requires schema 2.5; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
5972
+ line: location.line,
5973
+ column: location.column
5974
+ });
5975
+ }
5976
+ function isSchemaOlderThan(sourceSchemaVersion, requiredVersion) {
5977
+ return schemaVersionRank(sourceSchemaVersion) < schemaVersionRank(requiredVersion);
5978
+ }
5979
+ function schemaVersionRank(version) {
5980
+ switch (version) {
5981
+ case "2.0-draft":
5982
+ return 0;
5983
+ case "2.0":
5984
+ return 1;
5985
+ case "2.1":
5986
+ return 2;
5987
+ case "2.5":
5988
+ return 3;
5989
+ }
5990
+ }
5554
5991
  function preprocessAtlasSource(source) {
5555
5992
  const chars = [...source];
5556
5993
  const comments = [];
@@ -5638,7 +6075,7 @@ var WorldOrbit = (() => {
5638
6075
  }
5639
6076
 
5640
6077
  // packages/core/dist/atlas-edit.js
5641
- function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.0") {
6078
+ function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.5") {
5642
6079
  return {
5643
6080
  format: "worldorbit",
5644
6081
  version,
@@ -5985,8 +6422,9 @@ var WorldOrbit = (() => {
5985
6422
  }
5986
6423
 
5987
6424
  // packages/core/dist/load.js
5988
- var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1)?$/i;
6425
+ var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1|\.5)?$/i;
5989
6426
  var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
6427
+ var ATLAS_SCHEMA_25_PATTERN = /^schema\s+2\.5$/i;
5990
6428
  var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
5991
6429
  function detectWorldOrbitSchemaVersion(source) {
5992
6430
  for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
@@ -6000,6 +6438,9 @@ var WorldOrbit = (() => {
6000
6438
  if (ATLAS_SCHEMA_21_PATTERN.test(trimmed)) {
6001
6439
  return "2.1";
6002
6440
  }
6441
+ if (ATLAS_SCHEMA_25_PATTERN.test(trimmed)) {
6442
+ return "2.5";
6443
+ }
6003
6444
  if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
6004
6445
  return "2.0";
6005
6446
  }
@@ -6060,7 +6501,7 @@ var WorldOrbit = (() => {
6060
6501
  }
6061
6502
  function loadWorldOrbitSourceWithDiagnostics(source) {
6062
6503
  const schemaVersion = detectWorldOrbitSchemaVersion(source);
6063
- if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1") {
6504
+ if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5") {
6064
6505
  return loadAtlasSourceWithDiagnostics(source, schemaVersion);
6065
6506
  }
6066
6507
  let ast;
@@ -6367,13 +6808,14 @@ var WorldOrbit = (() => {
6367
6808
  }
6368
6809
  function createAtlasStateSnapshot(viewerState, renderOptions, filter, viewpointId) {
6369
6810
  return {
6370
- version: "2.0",
6811
+ version: "2.5",
6371
6812
  viewpointId,
6372
6813
  activeEventId: renderOptions.activeEventId ?? null,
6373
6814
  viewerState: { ...viewerState },
6374
6815
  renderOptions: {
6375
6816
  preset: renderOptions.preset,
6376
6817
  projection: renderOptions.projection,
6818
+ camera: renderOptions.camera ? { ...renderOptions.camera } : null,
6377
6819
  layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
6378
6820
  scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
6379
6821
  activeEventId: renderOptions.activeEventId ?? null
@@ -6387,7 +6829,7 @@ var WorldOrbit = (() => {
6387
6829
  function deserializeViewerAtlasState(serialized) {
6388
6830
  const raw = JSON.parse(decodeURIComponent(serialized));
6389
6831
  return {
6390
- version: "2.0",
6832
+ version: raw.version === "2.0" ? "2.0" : "2.5",
6391
6833
  viewpointId: raw.viewpointId ?? null,
6392
6834
  activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null,
6393
6835
  viewerState: {
@@ -6400,6 +6842,7 @@ var WorldOrbit = (() => {
6400
6842
  renderOptions: {
6401
6843
  preset: raw.renderOptions?.preset,
6402
6844
  projection: raw.renderOptions?.projection,
6845
+ camera: raw.renderOptions?.camera ? { ...raw.renderOptions.camera } : null,
6403
6846
  layers: raw.renderOptions?.layers ? { ...raw.renderOptions.layers } : void 0,
6404
6847
  scaleModel: raw.renderOptions?.scaleModel ? { ...raw.renderOptions.scaleModel } : void 0,
6405
6848
  activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null
@@ -6417,6 +6860,7 @@ var WorldOrbit = (() => {
6417
6860
  viewerState: { ...atlasState.viewerState },
6418
6861
  renderOptions: {
6419
6862
  ...atlasState.renderOptions,
6863
+ camera: atlasState.renderOptions.camera ? { ...atlasState.renderOptions.camera } : null,
6420
6864
  layers: atlasState.renderOptions.layers ? { ...atlasState.renderOptions.layers } : void 0,
6421
6865
  scaleModel: atlasState.renderOptions.scaleModel ? { ...atlasState.renderOptions.scaleModel } : void 0,
6422
6866
  activeEventId: atlasState.renderOptions.activeEventId ?? null
@@ -7529,6 +7973,7 @@ var WorldOrbit = (() => {
7529
7973
  padding: options.padding,
7530
7974
  preset: options.preset,
7531
7975
  projection: options.projection,
7976
+ camera: options.camera ? { ...options.camera } : null,
7532
7977
  scaleModel: options.scaleModel ? { ...options.scaleModel } : void 0,
7533
7978
  theme: options.theme,
7534
7979
  layers: options.layers,
@@ -7817,6 +8262,11 @@ var WorldOrbit = (() => {
7817
8262
  if (currentInput.kind !== "scene" && viewpoint.projection !== scene.projection) {
7818
8263
  nextRenderOptions.projection = viewpoint.projection;
7819
8264
  }
8265
+ if (viewpoint.camera) {
8266
+ nextRenderOptions.camera = { ...viewpoint.camera };
8267
+ } else if (renderOptions.camera) {
8268
+ nextRenderOptions.camera = null;
8269
+ }
7820
8270
  if (viewpointLayers) {
7821
8271
  nextRenderOptions.layers = viewpointLayers;
7822
8272
  }
@@ -8423,6 +8873,7 @@ var WorldOrbit = (() => {
8423
8873
  function cloneRenderOptions(renderOptions) {
8424
8874
  return {
8425
8875
  ...renderOptions,
8876
+ camera: renderOptions.camera ? { ...renderOptions.camera } : null,
8426
8877
  filter: renderOptions.filter ? { ...renderOptions.filter } : void 0,
8427
8878
  scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
8428
8879
  layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
@@ -8434,6 +8885,7 @@ var WorldOrbit = (() => {
8434
8885
  return {
8435
8886
  ...current,
8436
8887
  ...next,
8888
+ camera: next.camera !== void 0 ? next.camera ? { ...next.camera } : null : current.camera ? { ...current.camera } : null,
8437
8889
  filter: next.filter !== void 0 ? normalizeViewerFilter(next.filter) : current.filter ? { ...current.filter } : void 0,
8438
8890
  scaleModel: next.scaleModel ? {
8439
8891
  ...current.scaleModel ?? {},
@@ -8447,7 +8899,7 @@ var WorldOrbit = (() => {
8447
8899
  };
8448
8900
  }
8449
8901
  function hasSceneAffectingRenderOptions(options) {
8450
- return options.width !== void 0 || options.height !== void 0 || options.padding !== void 0 || options.preset !== void 0 || options.projection !== void 0 || options.scaleModel !== void 0 || options.activeEventId !== void 0;
8902
+ return options.width !== void 0 || options.height !== void 0 || options.padding !== void 0 || options.preset !== void 0 || options.projection !== void 0 || options.camera !== void 0 || options.scaleModel !== void 0 || options.activeEventId !== void 0;
8451
8903
  }
8452
8904
  function resolveSourceRenderOptions2(loaded, renderOptions) {
8453
8905
  const atlasDocument = loaded.atlasDocument ?? loaded.draftDocument;
@@ -9044,6 +9496,8 @@ var WorldOrbit = (() => {
9044
9496
  }
9045
9497
  function buildInspectorSnapshot() {
9046
9498
  const activeViewer = requireViewer();
9499
+ const scene = activeViewer.getScene();
9500
+ const camera = scene.camera;
9047
9501
  return {
9048
9502
  selection: activeViewer.getSelectionDetails(),
9049
9503
  activeViewpoint: activeViewer.getActiveViewpoint(),
@@ -9051,14 +9505,21 @@ var WorldOrbit = (() => {
9051
9505
  atlasState: activeViewer.getAtlasState(),
9052
9506
  visibleObjectIds: activeViewer.getVisibleObjects().map((object) => object.objectId),
9053
9507
  scene: {
9054
- title: activeViewer.getScene().title,
9055
- projection: activeViewer.getScene().projection,
9056
- renderPreset: activeViewer.getScene().renderPreset,
9057
- groupCount: activeViewer.getScene().groups.length,
9058
- semanticGroupCount: activeViewer.getScene().semanticGroups.length,
9059
- relationCount: activeViewer.getScene().relations.length,
9060
- eventCount: activeViewer.getScene().events.length,
9061
- viewpointCount: activeViewer.getScene().viewpoints.length
9508
+ title: scene.title,
9509
+ projection: scene.projection,
9510
+ renderProjection: scene.renderProjection,
9511
+ camera: camera ? {
9512
+ azimuth: camera.azimuth,
9513
+ elevation: camera.elevation,
9514
+ roll: camera.roll,
9515
+ distance: camera.distance
9516
+ } : null,
9517
+ renderPreset: scene.renderPreset,
9518
+ groupCount: scene.groups.length,
9519
+ semanticGroupCount: scene.semanticGroups.length,
9520
+ relationCount: scene.relations.length,
9521
+ eventCount: scene.events.length,
9522
+ viewpointCount: scene.viewpoints.length
9062
9523
  }
9063
9524
  };
9064
9525
  }