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.
@@ -945,7 +945,9 @@ var WorldOrbit = (() => {
945
945
  const height = frame.height;
946
946
  const padding = frame.padding;
947
947
  const layoutPreset = resolveLayoutPreset(document2);
948
- const projection = resolveProjection(document2, options.projection);
948
+ const schemaProjection = resolveProjection(document2, options.projection);
949
+ const camera = normalizeViewCamera(options.camera ?? null);
950
+ const renderProjection = resolveRenderProjection(schemaProjection, camera);
949
951
  const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
950
952
  const spacingFactor = layoutPresetSpacing(layoutPreset);
951
953
  const systemId = document2.system?.id ?? null;
@@ -988,7 +990,7 @@ var WorldOrbit = (() => {
988
990
  surfaceChildren,
989
991
  objectMap,
990
992
  spacingFactor,
991
- projection,
993
+ projection: renderProjection,
992
994
  scaleModel
993
995
  };
994
996
  const primaryRoot = rootObjects.find((object) => object.type === "star") ?? rootObjects[0] ?? null;
@@ -1000,7 +1002,7 @@ var WorldOrbit = (() => {
1000
1002
  const rootRingRadius = Math.min(width, height) * 0.28 * spacingFactor * scaleModel.orbitDistanceMultiplier;
1001
1003
  secondaryRoots.forEach((object, index) => {
1002
1004
  const angle = angleForIndex(index, secondaryRoots.length, -Math.PI / 2);
1003
- const offset = projectPolarOffset(angle, rootRingRadius, projection, 1);
1005
+ const offset = projectPolarOffset(angle, rootRingRadius, renderProjection, 1);
1004
1006
  placeObject(object, centerX + offset.x, centerY + offset.y, 0, positions, orbitDrafts, leaderDrafts, context);
1005
1007
  });
1006
1008
  }
@@ -1062,27 +1064,34 @@ var WorldOrbit = (() => {
1062
1064
  const layers = createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels);
1063
1065
  const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, scaleModel.labelMultiplier);
1064
1066
  const semanticGroups = createSceneSemanticGroups(document2, objects);
1065
- const viewpoints = createSceneViewpoints(document2, projection, frame.preset, relationships, objectMap);
1067
+ const viewpoints = createSceneViewpoints(document2, schemaProjection, frame.preset, relationships, objectMap);
1066
1068
  const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, scaleModel.labelMultiplier);
1067
1069
  return {
1068
1070
  width,
1069
1071
  height,
1070
1072
  padding,
1071
1073
  renderPreset: frame.preset,
1072
- projection,
1074
+ projection: schemaProjection,
1075
+ renderProjection,
1076
+ camera,
1073
1077
  scaleModel,
1074
1078
  title: String(document2.system?.title ?? document2.system?.properties.title ?? document2.system?.id ?? "WorldOrbit") || "WorldOrbit",
1075
- subtitle: `${capitalizeLabel(projection)} view - ${capitalizeLabel(layoutPreset)} layout`,
1079
+ subtitle: buildSceneSubtitle(schemaProjection, renderProjection, layoutPreset, camera),
1076
1080
  systemId,
1077
- viewMode: projection,
1081
+ viewMode: schemaProjection,
1078
1082
  layoutPreset,
1079
1083
  metadata: {
1080
1084
  format: document2.format,
1081
1085
  version: document2.version,
1082
- view: projection,
1086
+ view: schemaProjection,
1087
+ renderProjection,
1083
1088
  scale: String(document2.system?.properties.scale ?? layoutPreset),
1084
1089
  units: String(document2.system?.properties.units ?? "mixed"),
1085
- preset: frame.preset ?? "custom"
1090
+ preset: frame.preset ?? "custom",
1091
+ ...camera?.azimuth !== null ? { "camera.azimuth": String(camera?.azimuth) } : {},
1092
+ ...camera?.elevation !== null ? { "camera.elevation": String(camera?.elevation) } : {},
1093
+ ...camera?.roll !== null ? { "camera.roll": String(camera?.roll) } : {},
1094
+ ...camera?.distance !== null ? { "camera.distance": String(camera?.distance) } : {}
1086
1095
  },
1087
1096
  contentBounds,
1088
1097
  layers,
@@ -1119,21 +1128,42 @@ var WorldOrbit = (() => {
1119
1128
  return cloned;
1120
1129
  }
1121
1130
  const objectMap = new Map(cloned.map((object) => [object.id, object]));
1131
+ const referencedIds = /* @__PURE__ */ new Set([
1132
+ ...activeEvent.targetObjectId ? [activeEvent.targetObjectId] : [],
1133
+ ...activeEvent.participantObjectIds,
1134
+ ...activeEvent.positions.map((pose) => pose.objectId)
1135
+ ]);
1136
+ for (const objectId of referencedIds) {
1137
+ const object = objectMap.get(objectId);
1138
+ if (!object) {
1139
+ continue;
1140
+ }
1141
+ if (activeEvent.epoch) {
1142
+ object.epoch = activeEvent.epoch;
1143
+ }
1144
+ if (activeEvent.referencePlane) {
1145
+ object.referencePlane = activeEvent.referencePlane;
1146
+ }
1147
+ }
1122
1148
  for (const pose of activeEvent.positions) {
1123
1149
  const object = objectMap.get(pose.objectId);
1124
1150
  if (!object) {
1125
1151
  continue;
1126
1152
  }
1127
- object.placement = pose.placement ? structuredClone(pose.placement) : null;
1153
+ if (pose.placement) {
1154
+ object.placement = structuredClone(pose.placement);
1155
+ }
1128
1156
  if (pose.inner) {
1129
1157
  object.properties.inner = { ...pose.inner };
1130
- } else {
1131
- delete object.properties.inner;
1132
1158
  }
1133
1159
  if (pose.outer) {
1134
1160
  object.properties.outer = { ...pose.outer };
1135
- } else {
1136
- delete object.properties.outer;
1161
+ }
1162
+ if (pose.epoch) {
1163
+ object.epoch = pose.epoch;
1164
+ }
1165
+ if (pose.referencePlane) {
1166
+ object.referencePlane = pose.referencePlane;
1137
1167
  }
1138
1168
  }
1139
1169
  return cloned;
@@ -1174,10 +1204,59 @@ var WorldOrbit = (() => {
1174
1204
  }
1175
1205
  }
1176
1206
  function resolveProjection(document2, projection) {
1177
- if (projection === "topdown" || projection === "isometric") {
1207
+ if (projection === "topdown" || projection === "isometric" || projection === "orthographic" || projection === "perspective") {
1178
1208
  return projection;
1179
1209
  }
1180
- return String(document2.system?.properties.view ?? "topdown").toLowerCase() === "isometric" ? "isometric" : "topdown";
1210
+ const documentView = String(document2.system?.properties.view ?? "topdown").toLowerCase();
1211
+ return parseViewProjection(documentView) ?? "topdown";
1212
+ }
1213
+ function resolveRenderProjection(projection, camera) {
1214
+ switch (projection) {
1215
+ case "topdown":
1216
+ return "topdown";
1217
+ case "isometric":
1218
+ return "isometric";
1219
+ case "orthographic":
1220
+ return camera && (camera.azimuth !== null || camera.elevation !== null || camera.roll !== null) ? "isometric" : "topdown";
1221
+ case "perspective":
1222
+ return "isometric";
1223
+ }
1224
+ }
1225
+ function normalizeViewCamera(camera) {
1226
+ if (!camera) {
1227
+ return null;
1228
+ }
1229
+ const normalized = {
1230
+ azimuth: normalizeFiniteCameraValue(camera.azimuth),
1231
+ elevation: normalizeFiniteCameraValue(camera.elevation),
1232
+ roll: normalizeFiniteCameraValue(camera.roll),
1233
+ distance: normalizePositiveCameraDistance(camera.distance)
1234
+ };
1235
+ return normalized.azimuth !== null || normalized.elevation !== null || normalized.roll !== null || normalized.distance !== null ? normalized : null;
1236
+ }
1237
+ function normalizeFiniteCameraValue(value) {
1238
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1239
+ }
1240
+ function normalizePositiveCameraDistance(value) {
1241
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
1242
+ }
1243
+ function buildSceneSubtitle(projection, renderProjection, layoutPreset, camera) {
1244
+ const parts = [`${capitalizeLabel(projection)} view`, `${capitalizeLabel(layoutPreset)} layout`];
1245
+ if (projection !== renderProjection) {
1246
+ parts.push(`2D ${renderProjection} fallback`);
1247
+ }
1248
+ if (camera) {
1249
+ const cameraParts = [
1250
+ camera.azimuth !== null ? `az ${camera.azimuth}` : null,
1251
+ camera.elevation !== null ? `el ${camera.elevation}` : null,
1252
+ camera.roll !== null ? `roll ${camera.roll}` : null,
1253
+ camera.distance !== null ? `dist ${camera.distance}` : null
1254
+ ].filter(Boolean);
1255
+ if (cameraParts.length > 0) {
1256
+ parts.push(`camera ${cameraParts.join(" / ")}`);
1257
+ }
1258
+ }
1259
+ return parts.join(" - ");
1181
1260
  }
1182
1261
  function resolveScaleModel(layoutPreset, overrides) {
1183
1262
  const defaults = defaultScaleModel(layoutPreset);
@@ -1613,6 +1692,8 @@ var WorldOrbit = (() => {
1613
1692
  function createGeneratedOverviewViewpoint(document2, projection, preset) {
1614
1693
  const title = document2.system?.title ?? document2.system?.properties.title;
1615
1694
  const label = title ? `${String(title)} Overview` : "Overview";
1695
+ const camera = normalizeViewCamera(null);
1696
+ const renderProjection = resolveRenderProjection(projection, camera);
1616
1697
  return {
1617
1698
  id: "overview",
1618
1699
  label,
@@ -1621,6 +1702,8 @@ var WorldOrbit = (() => {
1621
1702
  selectedObjectId: null,
1622
1703
  eventIds: [],
1623
1704
  projection,
1705
+ renderProjection,
1706
+ camera,
1624
1707
  preset,
1625
1708
  rotationDeg: 0,
1626
1709
  scale: null,
@@ -1670,6 +1753,30 @@ var WorldOrbit = (() => {
1670
1753
  case "angle":
1671
1754
  draft.rotationDeg = parseFiniteNumber(normalizedValue) ?? draft.rotationDeg ?? 0;
1672
1755
  return;
1756
+ case "camera.azimuth":
1757
+ draft.camera = {
1758
+ ...draft.camera ?? createEmptyViewCamera(),
1759
+ azimuth: parseFiniteNumber(normalizedValue)
1760
+ };
1761
+ return;
1762
+ case "camera.elevation":
1763
+ draft.camera = {
1764
+ ...draft.camera ?? createEmptyViewCamera(),
1765
+ elevation: parseFiniteNumber(normalizedValue)
1766
+ };
1767
+ return;
1768
+ case "camera.roll":
1769
+ draft.camera = {
1770
+ ...draft.camera ?? createEmptyViewCamera(),
1771
+ roll: parseFiniteNumber(normalizedValue)
1772
+ };
1773
+ return;
1774
+ case "camera.distance":
1775
+ draft.camera = {
1776
+ ...draft.camera ?? createEmptyViewCamera(),
1777
+ distance: parsePositiveNumber(normalizedValue)
1778
+ };
1779
+ return;
1673
1780
  case "zoom":
1674
1781
  case "scale":
1675
1782
  draft.scale = parsePositiveNumber(normalizedValue);
@@ -1709,6 +1816,9 @@ var WorldOrbit = (() => {
1709
1816
  const selectedObjectId = draft.select && objectMap.has(draft.select) ? draft.select : objectId;
1710
1817
  const filter = normalizeViewpointFilter(draft.filter);
1711
1818
  const label = draft.label?.trim() || humanizeIdentifier(draft.id);
1819
+ const resolvedProjection = draft.projection ?? projection;
1820
+ const camera = normalizeViewCamera(draft.camera ?? null);
1821
+ const renderProjection = resolveRenderProjection(resolvedProjection, camera);
1712
1822
  return {
1713
1823
  id: draft.id,
1714
1824
  label,
@@ -1716,7 +1826,9 @@ var WorldOrbit = (() => {
1716
1826
  objectId,
1717
1827
  selectedObjectId,
1718
1828
  eventIds: [...new Set(draft.eventIds ?? [])],
1719
- projection: draft.projection ?? projection,
1829
+ projection: resolvedProjection,
1830
+ renderProjection,
1831
+ camera,
1720
1832
  preset: draft.preset ?? preset,
1721
1833
  rotationDeg: draft.rotationDeg ?? 0,
1722
1834
  scale: draft.scale ?? null,
@@ -1733,6 +1845,14 @@ var WorldOrbit = (() => {
1733
1845
  groupIds: []
1734
1846
  };
1735
1847
  }
1848
+ function createEmptyViewCamera() {
1849
+ return {
1850
+ azimuth: null,
1851
+ elevation: null,
1852
+ roll: null,
1853
+ distance: null
1854
+ };
1855
+ }
1736
1856
  function normalizeViewpointFilter(filter) {
1737
1857
  if (!filter) {
1738
1858
  return null;
@@ -1746,7 +1866,18 @@ var WorldOrbit = (() => {
1746
1866
  return normalized.query || normalized.objectTypes.length > 0 || normalized.tags.length > 0 || normalized.groupIds.length > 0 ? normalized : null;
1747
1867
  }
1748
1868
  function parseViewProjection(value) {
1749
- return value.toLowerCase() === "isometric" ? "isometric" : value.toLowerCase() === "topdown" ? "topdown" : null;
1869
+ switch (value.toLowerCase()) {
1870
+ case "topdown":
1871
+ return "topdown";
1872
+ case "isometric":
1873
+ return "isometric";
1874
+ case "orthographic":
1875
+ return "orthographic";
1876
+ case "perspective":
1877
+ return "perspective";
1878
+ default:
1879
+ return null;
1880
+ }
1750
1881
  }
1751
1882
  function parseRenderPreset(value) {
1752
1883
  const normalized = value.toLowerCase();
@@ -1784,7 +1915,7 @@ var WorldOrbit = (() => {
1784
1915
  }
1785
1916
  function parseViewpointGroups(value, document2, relationships, objectMap) {
1786
1917
  return splitListValue(value).map((entry) => {
1787
- if (document2.schemaVersion === "2.1" || document2.groups.some((group) => group.id === entry)) {
1918
+ if (document2.schemaVersion === "2.1" || document2.schemaVersion === "2.5" || document2.groups.some((group) => group.id === entry)) {
1788
1919
  return entry;
1789
1920
  }
1790
1921
  if (entry.startsWith("wo-") && entry.endsWith("-group")) {
@@ -2577,8 +2708,8 @@ var WorldOrbit = (() => {
2577
2708
  }
2578
2709
  return {
2579
2710
  format: "worldorbit",
2580
- version: "2.0",
2581
- schemaVersion: "2.0",
2711
+ version: "2.5",
2712
+ schemaVersion: "2.5",
2582
2713
  sourceVersion: document2.version,
2583
2714
  system,
2584
2715
  groups: structuredClone(document2.groups ?? []),
@@ -2634,8 +2765,9 @@ var WorldOrbit = (() => {
2634
2765
  };
2635
2766
  }
2636
2767
  function createDraftDefaults(document2, preset, projection) {
2768
+ const rawView = typeof document2.system?.properties.view === "string" ? document2.system.properties.view.toLowerCase() : null;
2637
2769
  return {
2638
- view: typeof document2.system?.properties.view === "string" && document2.system.properties.view.toLowerCase() === "topdown" ? "topdown" : projection,
2770
+ view: rawView === "topdown" || rawView === "isometric" || rawView === "orthographic" || rawView === "perspective" ? rawView : projection,
2639
2771
  scale: typeof document2.system?.properties.scale === "string" ? document2.system.properties.scale : null,
2640
2772
  units: typeof document2.system?.properties.units === "string" ? document2.system.properties.units : null,
2641
2773
  preset,
@@ -2742,6 +2874,7 @@ var WorldOrbit = (() => {
2742
2874
  preset: viewpoint.preset,
2743
2875
  zoom: viewpoint.scale,
2744
2876
  rotationDeg: viewpoint.rotationDeg,
2877
+ camera: viewpoint.camera ? { ...viewpoint.camera } : null,
2745
2878
  layers: { ...viewpoint.layers },
2746
2879
  filter: viewpoint.filter ? {
2747
2880
  query: viewpoint.filter.query,
@@ -2783,7 +2916,9 @@ var WorldOrbit = (() => {
2783
2916
  objectId: pose.objectId,
2784
2917
  placement: clonePlacement(pose.placement),
2785
2918
  inner: pose.inner ? { ...pose.inner } : void 0,
2786
- outer: pose.outer ? { ...pose.outer } : void 0
2919
+ outer: pose.outer ? { ...pose.outer } : void 0,
2920
+ epoch: pose.epoch ?? null,
2921
+ referencePlane: pose.referencePlane ?? null
2787
2922
  };
2788
2923
  }
2789
2924
  function clonePlacement(placement) {
@@ -2798,21 +2933,42 @@ var WorldOrbit = (() => {
2798
2933
  return;
2799
2934
  }
2800
2935
  const objectMap = new Map(objects.map((object) => [object.id, object]));
2936
+ const referencedIds = /* @__PURE__ */ new Set([
2937
+ ...event.targetObjectId ? [event.targetObjectId] : [],
2938
+ ...event.participantObjectIds,
2939
+ ...event.positions.map((pose) => pose.objectId)
2940
+ ]);
2941
+ for (const objectId of referencedIds) {
2942
+ const object = objectMap.get(objectId);
2943
+ if (!object) {
2944
+ continue;
2945
+ }
2946
+ if (event.epoch) {
2947
+ object.epoch = event.epoch;
2948
+ }
2949
+ if (event.referencePlane) {
2950
+ object.referencePlane = event.referencePlane;
2951
+ }
2952
+ }
2801
2953
  for (const pose of event.positions) {
2802
2954
  const object = objectMap.get(pose.objectId);
2803
2955
  if (!object) {
2804
2956
  continue;
2805
2957
  }
2806
- object.placement = clonePlacement(pose.placement);
2958
+ if (pose.placement) {
2959
+ object.placement = clonePlacement(pose.placement);
2960
+ }
2807
2961
  if (pose.inner) {
2808
2962
  object.properties.inner = { ...pose.inner };
2809
- } else {
2810
- delete object.properties.inner;
2811
2963
  }
2812
2964
  if (pose.outer) {
2813
2965
  object.properties.outer = { ...pose.outer };
2814
- } else {
2815
- delete object.properties.outer;
2966
+ }
2967
+ if (pose.epoch) {
2968
+ object.epoch = pose.epoch;
2969
+ }
2970
+ if (pose.referencePlane) {
2971
+ object.referencePlane = pose.referencePlane;
2816
2972
  }
2817
2973
  }
2818
2974
  }
@@ -2897,6 +3053,18 @@ var WorldOrbit = (() => {
2897
3053
  if (viewpoint.rotationDeg !== 0) {
2898
3054
  info2[`${prefix}.rotation`] = String(viewpoint.rotationDeg);
2899
3055
  }
3056
+ if (viewpoint.camera?.azimuth !== null) {
3057
+ info2[`${prefix}.camera.azimuth`] = String(viewpoint.camera?.azimuth);
3058
+ }
3059
+ if (viewpoint.camera?.elevation !== null) {
3060
+ info2[`${prefix}.camera.elevation`] = String(viewpoint.camera?.elevation);
3061
+ }
3062
+ if (viewpoint.camera?.roll !== null) {
3063
+ info2[`${prefix}.camera.roll`] = String(viewpoint.camera?.roll);
3064
+ }
3065
+ if (viewpoint.camera?.distance !== null) {
3066
+ info2[`${prefix}.camera.distance`] = String(viewpoint.camera?.distance);
3067
+ }
2900
3068
  const serializedLayers = serializeViewpointLayers(viewpoint.layers);
2901
3069
  if (serializedLayers) {
2902
3070
  info2[`${prefix}.layers`] = serializedLayers;
@@ -2993,26 +3161,26 @@ var WorldOrbit = (() => {
2993
3161
  ];
2994
3162
  function formatDocument(document2, options = {}) {
2995
3163
  const schema = options.schema ?? "auto";
2996
- 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";
3164
+ 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";
2997
3165
  if (useDraft) {
2998
3166
  if (schema === "2.0-draft") {
2999
- const legacyDraftDocument = document2.version === "2.0-draft" ? document2 : document2.version === "2.0" || document2.version === "2.1" ? {
3167
+ const legacyDraftDocument = document2.version === "2.0-draft" ? document2 : document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" ? {
3000
3168
  ...document2,
3001
3169
  version: "2.0-draft",
3002
3170
  schemaVersion: "2.0-draft"
3003
3171
  } : upgradeDocumentToDraftV2(document2);
3004
3172
  return formatDraftDocument(legacyDraftDocument);
3005
3173
  }
3006
- const atlasDocument = document2.version === "2.0" || document2.version === "2.1" ? document2 : document2.version === "2.0-draft" ? {
3174
+ const atlasDocument = document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" ? document2 : document2.version === "2.0-draft" ? {
3007
3175
  ...document2,
3008
3176
  version: "2.0",
3009
3177
  schemaVersion: "2.0"
3010
3178
  } : upgradeDocumentToV2(document2);
3011
- if (schema === "2.1" && atlasDocument.version !== "2.1") {
3179
+ if ((schema === "2.0" || schema === "2.1" || schema === "2.5") && atlasDocument.version !== schema) {
3012
3180
  return formatAtlasDocument({
3013
3181
  ...atlasDocument,
3014
- version: "2.1",
3015
- schemaVersion: "2.1"
3182
+ version: schema,
3183
+ schemaVersion: schema
3016
3184
  });
3017
3185
  }
3018
3186
  return formatAtlasDocument(atlasDocument);
@@ -3289,6 +3457,21 @@ var WorldOrbit = (() => {
3289
3457
  if (viewpoint.rotationDeg !== 0) {
3290
3458
  lines.push(` rotation ${viewpoint.rotationDeg}`);
3291
3459
  }
3460
+ if (viewpoint.camera && hasCameraValues(viewpoint.camera)) {
3461
+ lines.push(" camera");
3462
+ if (viewpoint.camera.azimuth !== null) {
3463
+ lines.push(` azimuth ${viewpoint.camera.azimuth}`);
3464
+ }
3465
+ if (viewpoint.camera.elevation !== null) {
3466
+ lines.push(` elevation ${viewpoint.camera.elevation}`);
3467
+ }
3468
+ if (viewpoint.camera.roll !== null) {
3469
+ lines.push(` roll ${viewpoint.camera.roll}`);
3470
+ }
3471
+ if (viewpoint.camera.distance !== null) {
3472
+ lines.push(` distance ${viewpoint.camera.distance}`);
3473
+ }
3474
+ }
3292
3475
  const layerTokens = formatDraftLayers(viewpoint.layers);
3293
3476
  if (layerTokens.length > 0) {
3294
3477
  lines.push(` layers ${layerTokens.join(" ")}`);
@@ -3388,6 +3571,12 @@ var WorldOrbit = (() => {
3388
3571
  if (event.visibility) {
3389
3572
  lines.push(` visibility ${quoteIfNeeded(event.visibility)}`);
3390
3573
  }
3574
+ if (event.epoch) {
3575
+ lines.push(` epoch ${quoteIfNeeded(event.epoch)}`);
3576
+ }
3577
+ if (event.referencePlane) {
3578
+ lines.push(` referencePlane ${quoteIfNeeded(event.referencePlane)}`);
3579
+ }
3391
3580
  if (event.tags.length > 0) {
3392
3581
  lines.push(` tags ${event.tags.map(quoteIfNeeded).join(" ")}`);
3393
3582
  }
@@ -3412,10 +3601,15 @@ var WorldOrbit = (() => {
3412
3601
  function formatEventPoseFields(pose) {
3413
3602
  return [
3414
3603
  ...formatPlacement(pose.placement),
3604
+ ...pose.epoch ? [`epoch ${quoteIfNeeded(pose.epoch)}`] : [],
3605
+ ...pose.referencePlane ? [`referencePlane ${quoteIfNeeded(pose.referencePlane)}`] : [],
3415
3606
  ...formatOptionalUnit("inner", pose.inner),
3416
3607
  ...formatOptionalUnit("outer", pose.outer)
3417
3608
  ];
3418
3609
  }
3610
+ function hasCameraValues(camera) {
3611
+ return camera.azimuth !== null || camera.elevation !== null || camera.roll !== null || camera.distance !== null;
3612
+ }
3419
3613
  function formatValue(value) {
3420
3614
  if (Array.isArray(value)) {
3421
3615
  return value.map((item) => quoteIfNeeded(item)).join(" ");
@@ -3709,13 +3903,13 @@ var WorldOrbit = (() => {
3709
3903
  validateRelation(relation, objectMap, diagnostics);
3710
3904
  }
3711
3905
  for (const viewpoint of document2.system?.viewpoints ?? []) {
3712
- validateViewpoint(viewpoint.filter, viewpoint.events ?? [], groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpoint.id);
3906
+ validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap);
3713
3907
  }
3714
3908
  for (const object of document2.objects) {
3715
3909
  validateObject(object, document2.system, objectMap, groupIds, diagnostics);
3716
3910
  }
3717
3911
  for (const event of document2.events) {
3718
- validateEvent(event, objectMap, diagnostics);
3912
+ validateEvent(event, document2.system, objectMap, diagnostics);
3719
3913
  }
3720
3914
  return diagnostics;
3721
3915
  }
@@ -3734,21 +3928,24 @@ var WorldOrbit = (() => {
3734
3928
  diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
3735
3929
  }
3736
3930
  }
3737
- function validateViewpoint(filter, eventRefs, groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpointId) {
3738
- if (sourceSchemaVersion === "2.1") {
3931
+ function validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap) {
3932
+ const filter = viewpoint.filter;
3933
+ if (sourceSchemaVersion === "2.1" || sourceSchemaVersion === "2.5") {
3739
3934
  if (filter) {
3740
3935
  for (const groupId of filter.groupIds) {
3741
3936
  if (!groupIds.has(groupId)) {
3742
- diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.groups`));
3937
+ diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.groups`));
3743
3938
  }
3744
3939
  }
3745
3940
  }
3746
- for (const eventId of eventRefs) {
3941
+ for (const eventId of viewpoint.events ?? []) {
3747
3942
  if (!eventIds.has(eventId)) {
3748
- diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.events`));
3943
+ diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.events`));
3749
3944
  }
3750
3945
  }
3751
3946
  }
3947
+ validateProjection(viewpoint.projection, diagnostics, `viewpoint.${viewpoint.id}.projection`, viewpoint.id);
3948
+ validateCamera(viewpoint.camera, viewpoint.projection, viewpoint.rotationDeg, diagnostics, viewpoint.id, viewpoint.focusObjectId, viewpoint.selectedObjectId, filter, objectMap);
3752
3949
  }
3753
3950
  function validateObject(object, system, objectMap, groupIds, diagnostics) {
3754
3951
  const placement = object.placement;
@@ -3761,6 +3958,12 @@ var WorldOrbit = (() => {
3761
3958
  }
3762
3959
  }
3763
3960
  }
3961
+ if (typeof object.epoch === "string" && !object.epoch.trim()) {
3962
+ diagnostics.push(warn("validate.epoch.empty", `Object "${object.id}" defines an empty epoch string.`, object.id, "epoch"));
3963
+ }
3964
+ if (typeof object.referencePlane === "string" && !object.referencePlane.trim()) {
3965
+ diagnostics.push(warn("validate.referencePlane.empty", `Object "${object.id}" defines an empty reference plane string.`, object.id, "referencePlane"));
3966
+ }
3764
3967
  if (orbitPlacement) {
3765
3968
  if (!objectMap.has(orbitPlacement.target)) {
3766
3969
  diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
@@ -3832,12 +4035,18 @@ var WorldOrbit = (() => {
3832
4035
  }
3833
4036
  }
3834
4037
  }
3835
- function validateEvent(event, objectMap, diagnostics) {
4038
+ function validateEvent(event, system, objectMap, diagnostics) {
3836
4039
  const fieldPrefix = `event.${event.id}`;
3837
4040
  const referencedIds = /* @__PURE__ */ new Set();
3838
4041
  if (!event.kind.trim()) {
3839
4042
  diagnostics.push(error("validate.event.kind.required", `Event "${event.id}" is missing a "kind" value.`, void 0, `${fieldPrefix}.kind`));
3840
4043
  }
4044
+ if (typeof event.epoch === "string" && !event.epoch.trim()) {
4045
+ diagnostics.push(warn("validate.event.epoch.empty", `Event "${event.id}" defines an empty epoch string.`, void 0, `${fieldPrefix}.epoch`));
4046
+ }
4047
+ if (typeof event.referencePlane === "string" && !event.referencePlane.trim()) {
4048
+ diagnostics.push(warn("validate.event.referencePlane.empty", `Event "${event.id}" defines an empty reference plane string.`, void 0, `${fieldPrefix}.referencePlane`));
4049
+ }
3841
4050
  if (!event.targetObjectId && event.participantObjectIds.length === 0) {
3842
4051
  diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
3843
4052
  }
@@ -3884,10 +4093,14 @@ var WorldOrbit = (() => {
3884
4093
  if (!referencedIds.has(pose.objectId)) {
3885
4094
  diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
3886
4095
  }
3887
- validateEventPose(pose, object, objectMap, diagnostics, poseFieldPrefix, event.id);
4096
+ validateEventPose(pose, object, event, system, objectMap, diagnostics, poseFieldPrefix, event.id);
4097
+ }
4098
+ const missingPoseIds = [...referencedIds].filter((objectId) => !poseIds.has(objectId));
4099
+ if (event.positions.length > 0 && missingPoseIds.length > 0) {
4100
+ diagnostics.push(warn("validate.event.positions.partial", `Event "${event.id}" leaves ${missingPoseIds.length} referenced object(s) on their base placement.`, void 0, `${fieldPrefix}.positions`));
3888
4101
  }
3889
4102
  }
3890
- function validateEventPose(pose, object, objectMap, diagnostics, fieldPrefix, eventId) {
4103
+ function validateEventPose(pose, object, event, system, objectMap, diagnostics, fieldPrefix, eventId) {
3891
4104
  const placement = pose.placement;
3892
4105
  if (!placement) {
3893
4106
  diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
@@ -3900,6 +4113,15 @@ var WorldOrbit = (() => {
3900
4113
  if (placement.distance && placement.semiMajor) {
3901
4114
  diagnostics.push(error("validate.event.pose.orbit.distanceConflict", `Event "${eventId}" pose "${pose.objectId}" cannot declare both "distance" and "semiMajor".`, void 0, `${fieldPrefix}.distance`));
3902
4115
  }
4116
+ if (placement.phase && !resolveEffectiveEpoch(system, object, event, pose)) {
4117
+ diagnostics.push(warn("validate.event.pose.phase.epochMissing", `Event "${eventId}" pose "${pose.objectId}" sets "phase" without an effective epoch.`, void 0, `${fieldPrefix}.phase`));
4118
+ }
4119
+ if (placement.inclination && !resolveEffectiveReferencePlane(system, object, event, pose)) {
4120
+ diagnostics.push(warn("validate.event.pose.inclination.referencePlaneMissing", `Event "${eventId}" pose "${pose.objectId}" sets "inclination" without an effective reference plane.`, void 0, `${fieldPrefix}.inclination`));
4121
+ }
4122
+ if (placement.period && !massInSolar(objectMap.get(placement.target)?.properties.mass)) {
4123
+ 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`));
4124
+ }
3903
4125
  return;
3904
4126
  }
3905
4127
  if (placement.mode === "surface") {
@@ -4034,6 +4256,52 @@ var WorldOrbit = (() => {
4034
4256
  return null;
4035
4257
  }
4036
4258
  }
4259
+ function validateProjection(projection, diagnostics, field, viewpointId) {
4260
+ if (projection !== "topdown" && projection !== "isometric" && projection !== "orthographic" && projection !== "perspective") {
4261
+ diagnostics.push(error("validate.viewpoint.projection.invalid", `Unknown projection "${String(projection)}" in viewpoint "${viewpointId}".`, void 0, field));
4262
+ }
4263
+ }
4264
+ function validateCamera(camera, projection, rotationDeg, diagnostics, viewpointId, focusObjectId, selectedObjectId, filter, objectMap) {
4265
+ if (!camera) {
4266
+ return;
4267
+ }
4268
+ const prefix = `viewpoint.${viewpointId}.camera`;
4269
+ for (const [key, value] of [
4270
+ ["azimuth", camera.azimuth],
4271
+ ["elevation", camera.elevation],
4272
+ ["roll", camera.roll],
4273
+ ["distance", camera.distance]
4274
+ ]) {
4275
+ if (value !== null && (!Number.isFinite(value) || key === "distance" && value <= 0)) {
4276
+ diagnostics.push(error("validate.viewpoint.camera.invalid", `Invalid camera ${key} "${String(value)}" in viewpoint "${viewpointId}".`, void 0, `${prefix}.${key}`));
4277
+ }
4278
+ }
4279
+ if (camera.distance !== null && projection !== "perspective") {
4280
+ 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`));
4281
+ }
4282
+ if (projection === "topdown" && (camera.elevation !== null || camera.roll !== null)) {
4283
+ 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));
4284
+ }
4285
+ if (projection === "isometric" && camera.elevation !== null) {
4286
+ diagnostics.push(info("validate.viewpoint.camera.isometricStored", `Camera elevation on isometric viewpoint "${viewpointId}" is preserved semantically for future 3D rendering.`, void 0, `${prefix}.elevation`));
4287
+ }
4288
+ if (camera.azimuth !== null && camera.azimuth !== 0 && rotationDeg !== 0) {
4289
+ 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`));
4290
+ }
4291
+ const hasAnchor = focusObjectId !== null && objectMap.has(focusObjectId) || selectedObjectId !== null && objectMap.has(selectedObjectId) || !!filter;
4292
+ if (!hasAnchor) {
4293
+ diagnostics.push(info("validate.viewpoint.camera.anchorMissing", `Viewpoint "${viewpointId}" stores camera settings without a focus object, selection, or filter anchor.`, void 0, prefix));
4294
+ }
4295
+ }
4296
+ function resolveEffectiveEpoch(system, object, event, pose) {
4297
+ return normalizeOptionalContextString(pose?.epoch) ?? normalizeOptionalContextString(event?.epoch) ?? normalizeOptionalContextString(object.epoch) ?? normalizeOptionalContextString(system?.epoch) ?? null;
4298
+ }
4299
+ function resolveEffectiveReferencePlane(system, object, event, pose) {
4300
+ return normalizeOptionalContextString(pose?.referencePlane) ?? normalizeOptionalContextString(event?.referencePlane) ?? normalizeOptionalContextString(object.referencePlane) ?? normalizeOptionalContextString(system?.referencePlane) ?? null;
4301
+ }
4302
+ function normalizeOptionalContextString(value) {
4303
+ return typeof value === "string" && value.trim() ? value.trim() : null;
4304
+ }
4037
4305
  function toleranceForField(object, field) {
4038
4306
  const tolerance = object.tolerances?.find((entry) => entry.field === field)?.value;
4039
4307
  if (typeof tolerance === "number") {
@@ -4142,7 +4410,9 @@ var WorldOrbit = (() => {
4142
4410
  "surface",
4143
4411
  "free",
4144
4412
  "inner",
4145
- "outer"
4413
+ "outer",
4414
+ "epoch",
4415
+ "referencePlane"
4146
4416
  ]);
4147
4417
  function parseWorldOrbitAtlas(source) {
4148
4418
  return parseAtlasSource(source);
@@ -4184,7 +4454,7 @@ var WorldOrbit = (() => {
4184
4454
  if (!sawSchemaHeader) {
4185
4455
  sourceSchemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
4186
4456
  sawSchemaHeader = true;
4187
- if (prepared.comments.length > 0 && sourceSchemaVersion !== "2.1") {
4457
+ if (prepared.comments.length > 0 && isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
4188
4458
  diagnostics.push({
4189
4459
  code: "parse.schema21.commentCompatibility",
4190
4460
  severity: "warning",
@@ -4254,11 +4524,11 @@ var WorldOrbit = (() => {
4254
4524
  return document2;
4255
4525
  }
4256
4526
  function assertDraftSchemaHeader(tokens, line) {
4257
- if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1"].includes(tokens[1].value.toLowerCase())) {
4258
- throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
4527
+ if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1", "2.5"].includes(tokens[1].value.toLowerCase())) {
4528
+ 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);
4259
4529
  }
4260
4530
  const version = tokens[1].value.toLowerCase();
4261
- return version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
4531
+ return version === "2.5" ? "2.5" : version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
4262
4532
  }
4263
4533
  function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
4264
4534
  const keyword = tokens[0]?.value.toLowerCase();
@@ -4278,6 +4548,8 @@ var WorldOrbit = (() => {
4278
4548
  return {
4279
4549
  kind: "defaults",
4280
4550
  system,
4551
+ sourceSchemaVersion,
4552
+ diagnostics,
4281
4553
  seenFields: /* @__PURE__ */ new Set()
4282
4554
  };
4283
4555
  case "atlas":
@@ -4370,6 +4642,7 @@ var WorldOrbit = (() => {
4370
4642
  preset: system.defaults.preset,
4371
4643
  zoom: null,
4372
4644
  rotationDeg: 0,
4645
+ camera: null,
4373
4646
  layers: {},
4374
4647
  filter: null
4375
4648
  };
@@ -4383,7 +4656,10 @@ var WorldOrbit = (() => {
4383
4656
  seenFields: /* @__PURE__ */ new Set(),
4384
4657
  inFilter: false,
4385
4658
  filterIndent: null,
4386
- seenFilterFields: /* @__PURE__ */ new Set()
4659
+ seenFilterFields: /* @__PURE__ */ new Set(),
4660
+ inCamera: false,
4661
+ cameraIndent: null,
4662
+ seenCameraFields: /* @__PURE__ */ new Set()
4387
4663
  };
4388
4664
  }
4389
4665
  function startAnnotationSection(tokens, line, system, annotationIds) {
@@ -4490,6 +4766,8 @@ var WorldOrbit = (() => {
4490
4766
  participantObjectIds: [],
4491
4767
  timing: null,
4492
4768
  visibility: null,
4769
+ epoch: null,
4770
+ referencePlane: null,
4493
4771
  tags: [],
4494
4772
  color: null,
4495
4773
  hidden: false,
@@ -4614,6 +4892,12 @@ var WorldOrbit = (() => {
4614
4892
  const value = joinFieldValue(tokens, line);
4615
4893
  switch (key) {
4616
4894
  case "view":
4895
+ if (isSchema25Projection(value)) {
4896
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "defaults.view", {
4897
+ line,
4898
+ column: tokens[0].column
4899
+ });
4900
+ }
4617
4901
  section.system.defaults.view = parseProjectionValue(value, line, tokens[0].column);
4618
4902
  return;
4619
4903
  case "scale":
@@ -4653,14 +4937,36 @@ var WorldOrbit = (() => {
4653
4937
  throw new WorldOrbitError(`Unknown atlas field "${tokens[0].value}"`, line, tokens[0].column);
4654
4938
  }
4655
4939
  function applyViewpointField2(section, indent, tokens, line) {
4940
+ if (section.inCamera && indent <= (section.cameraIndent ?? 0)) {
4941
+ section.inCamera = false;
4942
+ section.cameraIndent = null;
4943
+ }
4656
4944
  if (section.inFilter && indent <= (section.filterIndent ?? 0)) {
4657
4945
  section.inFilter = false;
4658
4946
  section.filterIndent = null;
4659
4947
  }
4948
+ if (section.inCamera) {
4949
+ applyViewpointCameraField(section, tokens, line);
4950
+ return;
4951
+ }
4660
4952
  if (section.inFilter) {
4661
4953
  applyViewpointFilterField(section, tokens, line);
4662
4954
  return;
4663
4955
  }
4956
+ if (tokens.length === 1 && tokens[0].value.toLowerCase() === "camera") {
4957
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
4958
+ line,
4959
+ column: tokens[0].column
4960
+ });
4961
+ if (section.seenFields.has("camera")) {
4962
+ throw new WorldOrbitError('Duplicate viewpoint field "camera"', line, tokens[0].column);
4963
+ }
4964
+ section.seenFields.add("camera");
4965
+ section.inCamera = true;
4966
+ section.cameraIndent = indent;
4967
+ section.viewpoint.camera = section.viewpoint.camera ?? createEmptyViewCamera2();
4968
+ return;
4969
+ }
4664
4970
  if (tokens.length === 1 && tokens[0].value.toLowerCase() === "filter") {
4665
4971
  if (section.seenFields.has("filter")) {
4666
4972
  throw new WorldOrbitError('Duplicate viewpoint field "filter"', line, tokens[0].column);
@@ -4686,6 +4992,12 @@ var WorldOrbit = (() => {
4686
4992
  section.viewpoint.selectedObjectId = value;
4687
4993
  return;
4688
4994
  case "projection":
4995
+ if (isSchema25Projection(value)) {
4996
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "projection", {
4997
+ line,
4998
+ column: tokens[0].column
4999
+ });
5000
+ }
4689
5001
  section.viewpoint.projection = parseProjectionValue(value, line, tokens[0].column);
4690
5002
  return;
4691
5003
  case "preset":
@@ -4697,6 +5009,13 @@ var WorldOrbit = (() => {
4697
5009
  case "rotation":
4698
5010
  section.viewpoint.rotationDeg = parseFiniteNumber2(value, line, tokens[0].column, "rotation");
4699
5011
  return;
5012
+ case "camera":
5013
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
5014
+ line,
5015
+ column: tokens[0].column
5016
+ });
5017
+ section.viewpoint.camera = parseInlineViewCamera(tokens.slice(1), line, section.viewpoint.camera);
5018
+ return;
4700
5019
  case "layers":
4701
5020
  section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line, section.sourceSchemaVersion, section.diagnostics);
4702
5021
  return;
@@ -4711,6 +5030,28 @@ var WorldOrbit = (() => {
4711
5030
  throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
4712
5031
  }
4713
5032
  }
5033
+ function applyViewpointCameraField(section, tokens, line) {
5034
+ const key = requireUniqueField(tokens, section.seenCameraFields, line);
5035
+ const value = joinFieldValue(tokens, line);
5036
+ const camera = section.viewpoint.camera ?? createEmptyViewCamera2();
5037
+ switch (key) {
5038
+ case "azimuth":
5039
+ camera.azimuth = parseFiniteNumber2(value, line, tokens[0].column, "camera.azimuth");
5040
+ break;
5041
+ case "elevation":
5042
+ camera.elevation = parseFiniteNumber2(value, line, tokens[0].column, "camera.elevation");
5043
+ break;
5044
+ case "roll":
5045
+ camera.roll = parseFiniteNumber2(value, line, tokens[0].column, "camera.roll");
5046
+ break;
5047
+ case "distance":
5048
+ camera.distance = parsePositiveNumber2(value, line, tokens[0].column, "camera.distance");
5049
+ break;
5050
+ default:
5051
+ throw new WorldOrbitError(`Unknown viewpoint camera field "${tokens[0].value}"`, line, tokens[0].column);
5052
+ }
5053
+ section.viewpoint.camera = camera;
5054
+ }
4714
5055
  function applyViewpointFilterField(section, tokens, line) {
4715
5056
  const key = requireUniqueField(tokens, section.seenFilterFields, line);
4716
5057
  const filter = section.viewpoint.filter ?? createEmptyViewpointFilter2();
@@ -4821,6 +5162,12 @@ var WorldOrbit = (() => {
4821
5162
  section.positionsIndent = null;
4822
5163
  }
4823
5164
  if (section.activePose) {
5165
+ if (tokens[0]?.value === "epoch" || tokens[0]?.value === "referencePlane") {
5166
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, `pose.${tokens[0].value}`, {
5167
+ line,
5168
+ column: tokens[0]?.column ?? 1
5169
+ });
5170
+ }
4824
5171
  section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
4825
5172
  return;
4826
5173
  }
@@ -4875,6 +5222,20 @@ var WorldOrbit = (() => {
4875
5222
  case "visibility":
4876
5223
  section.event.visibility = joinFieldValue(tokens, line);
4877
5224
  return;
5225
+ case "epoch":
5226
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.epoch", {
5227
+ line,
5228
+ column: tokens[0].column
5229
+ });
5230
+ section.event.epoch = joinFieldValue(tokens, line);
5231
+ return;
5232
+ case "referenceplane":
5233
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.referencePlane", {
5234
+ line,
5235
+ column: tokens[0].column
5236
+ });
5237
+ section.event.referencePlane = joinFieldValue(tokens, line);
5238
+ return;
4878
5239
  case "tags":
4879
5240
  section.event.tags = parseTokenList(tokens.slice(1), line, "tags");
4880
5241
  return;
@@ -5002,11 +5363,15 @@ var WorldOrbit = (() => {
5002
5363
  }
5003
5364
  function parseProjectionValue(value, line, column) {
5004
5365
  const normalized = value.toLowerCase();
5005
- if (normalized !== "topdown" && normalized !== "isometric") {
5366
+ if (normalized !== "topdown" && normalized !== "isometric" && normalized !== "orthographic" && normalized !== "perspective") {
5006
5367
  throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
5007
5368
  }
5008
5369
  return normalized;
5009
5370
  }
5371
+ function isSchema25Projection(value) {
5372
+ const normalized = value.toLowerCase();
5373
+ return normalized === "orthographic" || normalized === "perspective";
5374
+ }
5010
5375
  function parsePresetValue(value, line, column) {
5011
5376
  const normalized = value.toLowerCase();
5012
5377
  if (normalized === "diagram" || normalized === "presentation" || normalized === "atlas-card" || normalized === "markdown") {
@@ -5036,6 +5401,48 @@ var WorldOrbit = (() => {
5036
5401
  groupIds: []
5037
5402
  };
5038
5403
  }
5404
+ function createEmptyViewCamera2() {
5405
+ return {
5406
+ azimuth: null,
5407
+ elevation: null,
5408
+ roll: null,
5409
+ distance: null
5410
+ };
5411
+ }
5412
+ function parseInlineViewCamera(tokens, line, current) {
5413
+ if (tokens.length === 0 || tokens.length % 2 !== 0) {
5414
+ throw new WorldOrbitError('Field "camera" expects "<field> <value>" pairs', line, tokens[0]?.column ?? 1);
5415
+ }
5416
+ const camera = current ? { ...current } : createEmptyViewCamera2();
5417
+ const seen = /* @__PURE__ */ new Set();
5418
+ for (let index = 0; index < tokens.length; index += 2) {
5419
+ const fieldToken = tokens[index];
5420
+ const valueToken = tokens[index + 1];
5421
+ const key = fieldToken.value.toLowerCase();
5422
+ if (seen.has(key)) {
5423
+ throw new WorldOrbitError(`Duplicate viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
5424
+ }
5425
+ seen.add(key);
5426
+ const value = valueToken.value;
5427
+ switch (key) {
5428
+ case "azimuth":
5429
+ camera.azimuth = parseFiniteNumber2(value, line, fieldToken.column, "camera.azimuth");
5430
+ break;
5431
+ case "elevation":
5432
+ camera.elevation = parseFiniteNumber2(value, line, fieldToken.column, "camera.elevation");
5433
+ break;
5434
+ case "roll":
5435
+ camera.roll = parseFiniteNumber2(value, line, fieldToken.column, "camera.roll");
5436
+ break;
5437
+ case "distance":
5438
+ camera.distance = parsePositiveNumber2(value, line, fieldToken.column, "camera.distance");
5439
+ break;
5440
+ default:
5441
+ throw new WorldOrbitError(`Unknown viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
5442
+ }
5443
+ }
5444
+ return camera;
5445
+ }
5039
5446
  function parseInlineObjectFields(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
5040
5447
  const fields = [];
5041
5448
  let index = 0;
@@ -5168,7 +5575,7 @@ var WorldOrbit = (() => {
5168
5575
  object.tolerances = tolerances;
5169
5576
  if (typedBlocks && Object.keys(typedBlocks).length > 0)
5170
5577
  object.typedBlocks = typedBlocks;
5171
- if (sourceSchemaVersion !== "2.1") {
5578
+ if (isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
5172
5579
  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) {
5173
5580
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
5174
5581
  }
@@ -5184,23 +5591,25 @@ var WorldOrbit = (() => {
5184
5591
  };
5185
5592
  }
5186
5593
  function normalizeDraftEventPose(rawPose) {
5187
- const fieldMap = collectDraftFields(rawPose.fields);
5594
+ const fieldMap = collectDraftFields(rawPose.fields, "event-pose");
5188
5595
  const placement = extractPlacementFromFieldMap(fieldMap);
5189
5596
  return {
5190
5597
  objectId: rawPose.objectId,
5191
5598
  placement,
5192
5599
  inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
5193
- outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer")
5600
+ outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer"),
5601
+ epoch: parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]),
5602
+ referencePlane: parseOptionalJoinedValue(fieldMap.get("referencePlane")?.[0])
5194
5603
  };
5195
5604
  }
5196
- function collectDraftFields(fields) {
5605
+ function collectDraftFields(fields, _mode = "object") {
5197
5606
  const grouped = /* @__PURE__ */ new Map();
5198
5607
  for (const field of fields) {
5199
5608
  const spec = getDraftObjectFieldSpec(field.key);
5200
- if (!spec) {
5609
+ if (!spec && !EVENT_POSE_FIELD_KEYS.has(field.key)) {
5201
5610
  throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
5202
5611
  }
5203
- if (!spec.allowRepeat && grouped.has(field.key)) {
5612
+ if (!spec?.allowRepeat && grouped.has(field.key)) {
5204
5613
  throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
5205
5614
  }
5206
5615
  const existing = grouped.get(field.key) ?? [];
@@ -5377,7 +5786,7 @@ var WorldOrbit = (() => {
5377
5786
  }
5378
5787
  }
5379
5788
  function warnIfSchema21Feature(sourceSchemaVersion, diagnostics, featureName, location) {
5380
- if (sourceSchemaVersion === "2.1") {
5789
+ if (!isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
5381
5790
  return;
5382
5791
  }
5383
5792
  diagnostics.push({
@@ -5389,6 +5798,34 @@ var WorldOrbit = (() => {
5389
5798
  column: location.column
5390
5799
  });
5391
5800
  }
5801
+ function warnIfSchema25Feature(sourceSchemaVersion, diagnostics, featureName, location) {
5802
+ if (!isSchemaOlderThan(sourceSchemaVersion, "2.5")) {
5803
+ return;
5804
+ }
5805
+ diagnostics.push({
5806
+ code: "parse.schema25.featureCompatibility",
5807
+ severity: "warning",
5808
+ source: "parse",
5809
+ message: `Feature "${featureName}" requires schema 2.5; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
5810
+ line: location.line,
5811
+ column: location.column
5812
+ });
5813
+ }
5814
+ function isSchemaOlderThan(sourceSchemaVersion, requiredVersion) {
5815
+ return schemaVersionRank(sourceSchemaVersion) < schemaVersionRank(requiredVersion);
5816
+ }
5817
+ function schemaVersionRank(version) {
5818
+ switch (version) {
5819
+ case "2.0-draft":
5820
+ return 0;
5821
+ case "2.0":
5822
+ return 1;
5823
+ case "2.1":
5824
+ return 2;
5825
+ case "2.5":
5826
+ return 3;
5827
+ }
5828
+ }
5392
5829
  function preprocessAtlasSource(source) {
5393
5830
  const chars = [...source];
5394
5831
  const comments = [];
@@ -5476,7 +5913,7 @@ var WorldOrbit = (() => {
5476
5913
  }
5477
5914
 
5478
5915
  // packages/core/dist/atlas-edit.js
5479
- function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.0") {
5916
+ function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.5") {
5480
5917
  return {
5481
5918
  format: "worldorbit",
5482
5919
  version,
@@ -5695,8 +6132,9 @@ var WorldOrbit = (() => {
5695
6132
  }
5696
6133
 
5697
6134
  // packages/core/dist/load.js
5698
- var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1)?$/i;
6135
+ var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1|\.5)?$/i;
5699
6136
  var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
6137
+ var ATLAS_SCHEMA_25_PATTERN = /^schema\s+2\.5$/i;
5700
6138
  var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
5701
6139
  function detectWorldOrbitSchemaVersion(source) {
5702
6140
  for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
@@ -5710,6 +6148,9 @@ var WorldOrbit = (() => {
5710
6148
  if (ATLAS_SCHEMA_21_PATTERN.test(trimmed)) {
5711
6149
  return "2.1";
5712
6150
  }
6151
+ if (ATLAS_SCHEMA_25_PATTERN.test(trimmed)) {
6152
+ return "2.5";
6153
+ }
5713
6154
  if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
5714
6155
  return "2.0";
5715
6156
  }
@@ -5770,7 +6211,7 @@ var WorldOrbit = (() => {
5770
6211
  }
5771
6212
  function loadWorldOrbitSourceWithDiagnostics(source) {
5772
6213
  const schemaVersion = detectWorldOrbitSchemaVersion(source);
5773
- if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1") {
6214
+ if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5") {
5774
6215
  return loadAtlasSourceWithDiagnostics(source, schemaVersion);
5775
6216
  }
5776
6217
  let ast;
@@ -6011,13 +6452,14 @@ var WorldOrbit = (() => {
6011
6452
  }
6012
6453
  function createAtlasStateSnapshot(viewerState, renderOptions, filter, viewpointId) {
6013
6454
  return {
6014
- version: "2.0",
6455
+ version: "2.5",
6015
6456
  viewpointId,
6016
6457
  activeEventId: renderOptions.activeEventId ?? null,
6017
6458
  viewerState: { ...viewerState },
6018
6459
  renderOptions: {
6019
6460
  preset: renderOptions.preset,
6020
6461
  projection: renderOptions.projection,
6462
+ camera: renderOptions.camera ? { ...renderOptions.camera } : null,
6021
6463
  layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
6022
6464
  scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
6023
6465
  activeEventId: renderOptions.activeEventId ?? null
@@ -6031,7 +6473,7 @@ var WorldOrbit = (() => {
6031
6473
  function deserializeViewerAtlasState(serialized) {
6032
6474
  const raw = JSON.parse(decodeURIComponent(serialized));
6033
6475
  return {
6034
- version: "2.0",
6476
+ version: raw.version === "2.0" ? "2.0" : "2.5",
6035
6477
  viewpointId: raw.viewpointId ?? null,
6036
6478
  activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null,
6037
6479
  viewerState: {
@@ -6044,6 +6486,7 @@ var WorldOrbit = (() => {
6044
6486
  renderOptions: {
6045
6487
  preset: raw.renderOptions?.preset,
6046
6488
  projection: raw.renderOptions?.projection,
6489
+ camera: raw.renderOptions?.camera ? { ...raw.renderOptions.camera } : null,
6047
6490
  layers: raw.renderOptions?.layers ? { ...raw.renderOptions.layers } : void 0,
6048
6491
  scaleModel: raw.renderOptions?.scaleModel ? { ...raw.renderOptions.scaleModel } : void 0,
6049
6492
  activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null
@@ -6061,6 +6504,7 @@ var WorldOrbit = (() => {
6061
6504
  viewerState: { ...atlasState.viewerState },
6062
6505
  renderOptions: {
6063
6506
  ...atlasState.renderOptions,
6507
+ camera: atlasState.renderOptions.camera ? { ...atlasState.renderOptions.camera } : null,
6064
6508
  layers: atlasState.renderOptions.layers ? { ...atlasState.renderOptions.layers } : void 0,
6065
6509
  scaleModel: atlasState.renderOptions.scaleModel ? { ...atlasState.renderOptions.scaleModel } : void 0,
6066
6510
  activeEventId: atlasState.renderOptions.activeEventId ?? null
@@ -7156,6 +7600,7 @@ var WorldOrbit = (() => {
7156
7600
  padding: options.padding,
7157
7601
  preset: options.preset,
7158
7602
  projection: options.projection,
7603
+ camera: options.camera ? { ...options.camera } : null,
7159
7604
  scaleModel: options.scaleModel ? { ...options.scaleModel } : void 0,
7160
7605
  theme: options.theme,
7161
7606
  layers: options.layers,
@@ -7444,6 +7889,11 @@ var WorldOrbit = (() => {
7444
7889
  if (currentInput.kind !== "scene" && viewpoint.projection !== scene.projection) {
7445
7890
  nextRenderOptions.projection = viewpoint.projection;
7446
7891
  }
7892
+ if (viewpoint.camera) {
7893
+ nextRenderOptions.camera = { ...viewpoint.camera };
7894
+ } else if (renderOptions.camera) {
7895
+ nextRenderOptions.camera = null;
7896
+ }
7447
7897
  if (viewpointLayers) {
7448
7898
  nextRenderOptions.layers = viewpointLayers;
7449
7899
  }
@@ -8050,6 +8500,7 @@ var WorldOrbit = (() => {
8050
8500
  function cloneRenderOptions(renderOptions) {
8051
8501
  return {
8052
8502
  ...renderOptions,
8503
+ camera: renderOptions.camera ? { ...renderOptions.camera } : null,
8053
8504
  filter: renderOptions.filter ? { ...renderOptions.filter } : void 0,
8054
8505
  scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
8055
8506
  layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
@@ -8061,6 +8512,7 @@ var WorldOrbit = (() => {
8061
8512
  return {
8062
8513
  ...current,
8063
8514
  ...next,
8515
+ camera: next.camera !== void 0 ? next.camera ? { ...next.camera } : null : current.camera ? { ...current.camera } : null,
8064
8516
  filter: next.filter !== void 0 ? normalizeViewerFilter(next.filter) : current.filter ? { ...current.filter } : void 0,
8065
8517
  scaleModel: next.scaleModel ? {
8066
8518
  ...current.scaleModel ?? {},
@@ -8074,7 +8526,7 @@ var WorldOrbit = (() => {
8074
8526
  };
8075
8527
  }
8076
8528
  function hasSceneAffectingRenderOptions(options) {
8077
- 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;
8529
+ 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;
8078
8530
  }
8079
8531
  function resolveSourceRenderOptions(loaded, renderOptions) {
8080
8532
  const atlasDocument = loaded.atlasDocument ?? loaded.draftDocument;
@@ -8385,7 +8837,11 @@ var WorldOrbit = (() => {
8385
8837
  var FIELD_HELP = {
8386
8838
  "defaults-view": {
8387
8839
  description: "Sets the default camera projection for the atlas.",
8388
- references: ["Topdown = map-like", "Isometric = angled overview"]
8840
+ references: [
8841
+ "Topdown = map-like",
8842
+ "Isometric = angled overview",
8843
+ "Orthographic/Perspective = 3D-ready semantic views"
8844
+ ]
8389
8845
  },
8390
8846
  "defaults-scale": {
8391
8847
  description: "Chooses the overall spacing/style preset used by the renderer.",
@@ -8397,15 +8853,35 @@ var WorldOrbit = (() => {
8397
8853
  },
8398
8854
  "viewpoint-projection": {
8399
8855
  description: "Overrides the projection for this saved viewpoint.",
8400
- references: ["Topdown = flat orbital map", "Isometric = angled scene"]
8856
+ references: [
8857
+ "Topdown = flat orbital map",
8858
+ "Isometric = angled scene",
8859
+ "Orthographic/Perspective = stored with current 2D fallback"
8860
+ ]
8401
8861
  },
8402
8862
  "viewpoint-zoom": {
8403
8863
  description: "Controls how closely this viewpoint frames the system.",
8404
8864
  references: ["1 = scene fit", "2+ = close-up"]
8405
8865
  },
8406
8866
  "viewpoint-rotation": {
8407
- description: "Rotates the saved camera angle in degrees.",
8408
- references: ["90deg = quarter turn", "180deg = flip"]
8867
+ description: "Legacy 2D screen rotation. This is separate from the Schema 2.5 camera block.",
8868
+ references: ["90deg = quarter turn", "Use camera.azimuth for semantic view direction"]
8869
+ },
8870
+ "viewpoint-camera-azimuth": {
8871
+ description: "Horizontal camera direction in degrees for Schema 2.5 viewpoints.",
8872
+ references: ["0 = forward/default", "90 = quarter orbit around the scene"]
8873
+ },
8874
+ "viewpoint-camera-elevation": {
8875
+ description: "Vertical camera tilt in degrees for 3D-ready viewpoints.",
8876
+ references: ["0 = level", "30 = gentle look down"]
8877
+ },
8878
+ "viewpoint-camera-roll": {
8879
+ description: "Rolls the camera around its forward axis.",
8880
+ references: ["0 = upright", "15 = slight bank"]
8881
+ },
8882
+ "viewpoint-camera-distance": {
8883
+ description: "Semantic camera distance for perspective viewpoints.",
8884
+ references: ["4 = close", "12 = wide framing"]
8409
8885
  },
8410
8886
  "viewpoint-events": {
8411
8887
  description: "Lists event IDs that this viewpoint should feature in its detail panel.",
@@ -8431,6 +8907,14 @@ var WorldOrbit = (() => {
8431
8907
  description: "Notes where or how the event is visible.",
8432
8908
  references: ['"Visible from Naar"', '"Southern hemisphere only"']
8433
8909
  },
8910
+ "event-epoch": {
8911
+ description: "Optional event-wide epoch that event poses inherit unless they override it.",
8912
+ references: ['"JY-0001.0"', '"Naar bloom cycle year 18"']
8913
+ },
8914
+ "event-referencePlane": {
8915
+ description: "Optional event-wide reference plane for all poses in this snapshot.",
8916
+ references: ["ecliptic", "naar-equatorial"]
8917
+ },
8434
8918
  "event-viewpoints": {
8435
8919
  description: "Viewpoint IDs that should list this event prominently.",
8436
8920
  references: ["naar-system", "overview inner-system"]
@@ -8471,6 +8955,14 @@ var WorldOrbit = (() => {
8471
8955
  description: "Starting position of the object along its orbit.",
8472
8956
  references: ["0deg = start position", "180deg = opposite side"]
8473
8957
  },
8958
+ "pose-epoch": {
8959
+ description: "Overrides the effective epoch for this pose only.",
8960
+ references: ['"JY-0001.0"', "Falls back to event, object, then system"]
8961
+ },
8962
+ "pose-referencePlane": {
8963
+ description: "Overrides the effective reference plane for this pose only.",
8964
+ references: ["naar-equatorial", "Falls back to event, object, then system"]
8965
+ },
8474
8966
  "prop-radius": {
8475
8967
  description: "Visual body size or real-world-inspired radius value.",
8476
8968
  references: ["1re = Earth radius", "1sol = Sun radius"]
@@ -8665,6 +9157,8 @@ var WorldOrbit = (() => {
8665
9157
  participantObjectIds: [],
8666
9158
  timing: null,
8667
9159
  visibility: null,
9160
+ epoch: null,
9161
+ referencePlane: null,
8668
9162
  tags: [],
8669
9163
  color: null,
8670
9164
  hidden: false,
@@ -8689,6 +9183,7 @@ var WorldOrbit = (() => {
8689
9183
  preset: atlasDocument.system?.defaults.preset ?? null,
8690
9184
  zoom: null,
8691
9185
  rotationDeg: 0,
9186
+ camera: null,
8692
9187
  layers: {},
8693
9188
  filter: null
8694
9189
  };
@@ -9580,6 +10075,7 @@ var WorldOrbit = (() => {
9580
10075
  preset: readOptionalTextInput(form, "viewpoint-preset") ?? null,
9581
10076
  zoom: parseNullableNumber(readOptionalTextInput(form, "viewpoint-zoom")),
9582
10077
  rotationDeg: parseNullableNumber(readOptionalTextInput(form, "viewpoint-rotation")) ?? 0,
10078
+ camera: buildViewCameraFromForm(form),
9583
10079
  layers: {
9584
10080
  background: readCheckbox(form, "layer-background"),
9585
10081
  guides: readCheckbox(form, "layer-guides"),
@@ -9622,6 +10118,8 @@ var WorldOrbit = (() => {
9622
10118
  participantObjectIds: splitTokens(readOptionalTextInput(form, "event-participants")),
9623
10119
  timing: readOptionalTextInput(form, "event-timing"),
9624
10120
  visibility: readOptionalTextInput(form, "event-visibility"),
10121
+ epoch: readOptionalTextInput(form, "event-epoch"),
10122
+ referencePlane: readOptionalTextInput(form, "event-referencePlane"),
9625
10123
  tags: splitTokens(readOptionalTextInput(form, "event-tags")),
9626
10124
  color: readOptionalTextInput(form, "event-color"),
9627
10125
  hidden: readCheckbox(form, "event-hidden")
@@ -9644,7 +10142,9 @@ var WorldOrbit = (() => {
9644
10142
  const nextObjectId = readTextInput(form, "pose-object-id") || currentPose.objectId;
9645
10143
  const replacement = {
9646
10144
  objectId: nextObjectId,
9647
- placement: buildPlacementFromPoseForm(form, currentPose)
10145
+ placement: buildPlacementFromPoseForm(form, currentPose),
10146
+ epoch: readOptionalTextInput(form, "pose-epoch"),
10147
+ referencePlane: readOptionalTextInput(form, "pose-referencePlane")
9648
10148
  };
9649
10149
  const inner = parseOptionalUnit(readOptionalTextInput(form, "prop-inner"));
9650
10150
  const outer = parseOptionalUnit(readOptionalTextInput(form, "prop-outer"));
@@ -10014,7 +10514,9 @@ var WorldOrbit = (() => {
10014
10514
  <h2>Defaults</h2>
10015
10515
  ${renderInspectorSection("defaults", "basics", "Basics", `${renderSelectField("Projection", "defaults-view", [
10016
10516
  ["topdown", "Topdown"],
10017
- ["isometric", "Isometric"]
10517
+ ["isometric", "Isometric"],
10518
+ ["orthographic", "Orthographic"],
10519
+ ["perspective", "Perspective"]
10018
10520
  ], defaults?.view ?? "topdown")}
10019
10521
  ${renderTextField("Scale preset", "defaults-scale", defaults?.scale ?? "")}
10020
10522
  ${renderTextField("Units", "defaults-units", defaults?.units ?? "")}
@@ -10050,7 +10552,9 @@ var WorldOrbit = (() => {
10050
10552
  ${renderTextField("Selected object", "viewpoint-select", viewpoint.selectedObjectId ?? "")}
10051
10553
  ${renderSelectField("Projection", "viewpoint-projection", [
10052
10554
  ["topdown", "Topdown"],
10053
- ["isometric", "Isometric"]
10555
+ ["isometric", "Isometric"],
10556
+ ["orthographic", "Orthographic"],
10557
+ ["perspective", "Perspective"]
10054
10558
  ], viewpoint.projection)}
10055
10559
  ${renderSelectField("Preset", "viewpoint-preset", [
10056
10560
  ["", "Document default"],
@@ -10061,6 +10565,11 @@ var WorldOrbit = (() => {
10061
10565
  ], viewpoint.preset ?? "")}
10062
10566
  ${renderTextField("Zoom", "viewpoint-zoom", viewpoint.zoom === null ? "" : String(viewpoint.zoom))}
10063
10567
  ${renderTextField("Rotation", "viewpoint-rotation", String(viewpoint.rotationDeg))}`, true)}
10568
+ ${renderInspectorSection("viewpoint", "camera", "Camera", `${renderTextField("Azimuth", "viewpoint-camera-azimuth", viewpoint.camera?.azimuth === null || viewpoint.camera?.azimuth === void 0 ? "" : String(viewpoint.camera.azimuth))}
10569
+ ${renderTextField("Elevation", "viewpoint-camera-elevation", viewpoint.camera?.elevation === null || viewpoint.camera?.elevation === void 0 ? "" : String(viewpoint.camera.elevation))}
10570
+ ${renderTextField("Roll", "viewpoint-camera-roll", viewpoint.camera?.roll === null || viewpoint.camera?.roll === void 0 ? "" : String(viewpoint.camera.roll))}
10571
+ ${renderTextField("Distance", "viewpoint-camera-distance", viewpoint.camera?.distance === null || viewpoint.camera?.distance === void 0 ? "" : String(viewpoint.camera.distance))}
10572
+ <p class="wo-editor-inline-note">Rotation stays a 2D screen-rotation hint. The camera block stores Schema 2.5 view direction and framing.</p>`)}
10064
10573
  ${renderInspectorSection("viewpoint", "layers", "Layers", `<fieldset class="wo-editor-fieldset">
10065
10574
  <legend>Layers</legend>
10066
10575
  ${renderCheckboxField("Background", "layer-background", viewpoint.layers.background !== false)}
@@ -10095,6 +10604,8 @@ var WorldOrbit = (() => {
10095
10604
  ${renderTextField("Participants", "event-participants", eventEntry.participantObjectIds.join(" "))}
10096
10605
  ${renderTextField("Timing", "event-timing", eventEntry.timing ?? "")}
10097
10606
  ${renderTextField("Visibility", "event-visibility", eventEntry.visibility ?? "")}
10607
+ ${renderTextField("Epoch", "event-epoch", eventEntry.epoch ?? "")}
10608
+ ${renderTextField("Reference plane", "event-referencePlane", eventEntry.referencePlane ?? "")}
10098
10609
  ${renderTextField("Tags", "event-tags", eventEntry.tags.join(" "))}
10099
10610
  ${renderTextField("Color", "event-color", eventEntry.color ?? "")}
10100
10611
  ${renderCheckboxField("Hidden", "event-hidden", eventEntry.hidden === true)}`, true)}
@@ -10139,6 +10650,9 @@ var WorldOrbit = (() => {
10139
10650
  ${renderTextField("Phase", "placement-phase", pose.placement?.mode === "orbit" && pose.placement.phase ? formatUnitValue3(pose.placement.phase) : "")}
10140
10651
  ${renderTextField("Inner", "prop-inner", pose.inner ? formatUnitValue3(pose.inner) : "")}
10141
10652
  ${renderTextField("Outer", "prop-outer", pose.outer ? formatUnitValue3(pose.outer) : "")}`, true)}
10653
+ ${renderInspectorSection("event-pose", "context", "Context", `${renderTextField("Epoch", "pose-epoch", pose.epoch ?? "")}
10654
+ ${renderTextField("Reference plane", "pose-referencePlane", pose.referencePlane ?? "")}
10655
+ <p class="wo-editor-inline-note">Falls back to event, then object, then system context when left empty.</p>`)}
10142
10656
  </form>`;
10143
10657
  }
10144
10658
  function renderAnnotationInspector(formState, id) {
@@ -10323,6 +10837,15 @@ var WorldOrbit = (() => {
10323
10837
  const parsed = Number(value);
10324
10838
  return Number.isFinite(parsed) ? parsed : null;
10325
10839
  }
10840
+ function buildViewCameraFromForm(form) {
10841
+ const camera = {
10842
+ azimuth: parseNullableNumber(readOptionalTextInput(form, "viewpoint-camera-azimuth")),
10843
+ elevation: parseNullableNumber(readOptionalTextInput(form, "viewpoint-camera-elevation")),
10844
+ roll: parseNullableNumber(readOptionalTextInput(form, "viewpoint-camera-roll")),
10845
+ distance: parseNullableNumber(readOptionalTextInput(form, "viewpoint-camera-distance"))
10846
+ };
10847
+ return camera.azimuth !== null || camera.elevation !== null || camera.roll !== null || camera.distance !== null ? camera : null;
10848
+ }
10326
10849
  function parseObjectTypes(value) {
10327
10850
  const tokens = splitTokens(value);
10328
10851
  return tokens.filter((token) => OBJECT_TYPES.includes(token));
@@ -10905,6 +11428,21 @@ var WorldOrbit = (() => {
10905
11428
  return ["viewpoint-zoom"];
10906
11429
  case "rotationDeg":
10907
11430
  return ["viewpoint-rotation"];
11431
+ case "camera":
11432
+ return [
11433
+ "viewpoint-camera-azimuth",
11434
+ "viewpoint-camera-elevation",
11435
+ "viewpoint-camera-roll",
11436
+ "viewpoint-camera-distance"
11437
+ ];
11438
+ case "camera.azimuth":
11439
+ return ["viewpoint-camera-azimuth"];
11440
+ case "camera.elevation":
11441
+ return ["viewpoint-camera-elevation"];
11442
+ case "camera.roll":
11443
+ return ["viewpoint-camera-roll"];
11444
+ case "camera.distance":
11445
+ return ["viewpoint-camera-distance"];
10908
11446
  case "events":
10909
11447
  return ["viewpoint-events"];
10910
11448
  default:
@@ -10930,6 +11468,10 @@ var WorldOrbit = (() => {
10930
11468
  return ["event-timing"];
10931
11469
  case "visibility":
10932
11470
  return ["event-visibility"];
11471
+ case "epoch":
11472
+ return ["event-epoch"];
11473
+ case "referencePlane":
11474
+ return ["event-referencePlane"];
10933
11475
  case "tags":
10934
11476
  return ["event-tags"];
10935
11477
  case "color":
@@ -10958,6 +11500,12 @@ var WorldOrbit = (() => {
10958
11500
  if (field === "inner" || field === "outer") {
10959
11501
  return [`prop-${field}`];
10960
11502
  }
11503
+ if (field === "epoch") {
11504
+ return ["pose-epoch"];
11505
+ }
11506
+ if (field === "referencePlane") {
11507
+ return ["pose-referencePlane"];
11508
+ }
10961
11509
  return [];
10962
11510
  case "annotation":
10963
11511
  switch (field) {