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.
@@ -211,13 +211,14 @@ var WorldOrbit = (() => {
211
211
  }
212
212
  function createAtlasStateSnapshot(viewerState, renderOptions, filter, viewpointId) {
213
213
  return {
214
- version: "2.0",
214
+ version: "2.5",
215
215
  viewpointId,
216
216
  activeEventId: renderOptions.activeEventId ?? null,
217
217
  viewerState: { ...viewerState },
218
218
  renderOptions: {
219
219
  preset: renderOptions.preset,
220
220
  projection: renderOptions.projection,
221
+ camera: renderOptions.camera ? { ...renderOptions.camera } : null,
221
222
  layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
222
223
  scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
223
224
  activeEventId: renderOptions.activeEventId ?? null
@@ -231,7 +232,7 @@ var WorldOrbit = (() => {
231
232
  function deserializeViewerAtlasState(serialized) {
232
233
  const raw = JSON.parse(decodeURIComponent(serialized));
233
234
  return {
234
- version: "2.0",
235
+ version: raw.version === "2.0" ? "2.0" : "2.5",
235
236
  viewpointId: raw.viewpointId ?? null,
236
237
  activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null,
237
238
  viewerState: {
@@ -244,6 +245,7 @@ var WorldOrbit = (() => {
244
245
  renderOptions: {
245
246
  preset: raw.renderOptions?.preset,
246
247
  projection: raw.renderOptions?.projection,
248
+ camera: raw.renderOptions?.camera ? { ...raw.renderOptions.camera } : null,
247
249
  layers: raw.renderOptions?.layers ? { ...raw.renderOptions.layers } : void 0,
248
250
  scaleModel: raw.renderOptions?.scaleModel ? { ...raw.renderOptions.scaleModel } : void 0,
249
251
  activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null
@@ -261,6 +263,7 @@ var WorldOrbit = (() => {
261
263
  viewerState: { ...atlasState.viewerState },
262
264
  renderOptions: {
263
265
  ...atlasState.renderOptions,
266
+ camera: atlasState.renderOptions.camera ? { ...atlasState.renderOptions.camera } : null,
264
267
  layers: atlasState.renderOptions.layers ? { ...atlasState.renderOptions.layers } : void 0,
265
268
  scaleModel: atlasState.renderOptions.scaleModel ? { ...atlasState.renderOptions.scaleModel } : void 0,
266
269
  activeEventId: atlasState.renderOptions.activeEventId ?? null
@@ -1309,7 +1312,9 @@ var WorldOrbit = (() => {
1309
1312
  const height = frame.height;
1310
1313
  const padding = frame.padding;
1311
1314
  const layoutPreset = resolveLayoutPreset(document2);
1312
- const projection = resolveProjection(document2, options.projection);
1315
+ const schemaProjection = resolveProjection(document2, options.projection);
1316
+ const camera = normalizeViewCamera(options.camera ?? null);
1317
+ const renderProjection = resolveRenderProjection(schemaProjection, camera);
1313
1318
  const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
1314
1319
  const spacingFactor = layoutPresetSpacing(layoutPreset);
1315
1320
  const systemId = document2.system?.id ?? null;
@@ -1352,7 +1357,7 @@ var WorldOrbit = (() => {
1352
1357
  surfaceChildren,
1353
1358
  objectMap,
1354
1359
  spacingFactor,
1355
- projection,
1360
+ projection: renderProjection,
1356
1361
  scaleModel
1357
1362
  };
1358
1363
  const primaryRoot = rootObjects.find((object) => object.type === "star") ?? rootObjects[0] ?? null;
@@ -1364,7 +1369,7 @@ var WorldOrbit = (() => {
1364
1369
  const rootRingRadius = Math.min(width, height) * 0.28 * spacingFactor * scaleModel.orbitDistanceMultiplier;
1365
1370
  secondaryRoots.forEach((object, index) => {
1366
1371
  const angle = angleForIndex(index, secondaryRoots.length, -Math.PI / 2);
1367
- const offset = projectPolarOffset(angle, rootRingRadius, projection, 1);
1372
+ const offset = projectPolarOffset(angle, rootRingRadius, renderProjection, 1);
1368
1373
  placeObject(object, centerX + offset.x, centerY + offset.y, 0, positions, orbitDrafts, leaderDrafts, context);
1369
1374
  });
1370
1375
  }
@@ -1426,27 +1431,34 @@ var WorldOrbit = (() => {
1426
1431
  const layers = createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels);
1427
1432
  const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, scaleModel.labelMultiplier);
1428
1433
  const semanticGroups = createSceneSemanticGroups(document2, objects);
1429
- const viewpoints = createSceneViewpoints(document2, projection, frame.preset, relationships, objectMap);
1434
+ const viewpoints = createSceneViewpoints(document2, schemaProjection, frame.preset, relationships, objectMap);
1430
1435
  const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, scaleModel.labelMultiplier);
1431
1436
  return {
1432
1437
  width,
1433
1438
  height,
1434
1439
  padding,
1435
1440
  renderPreset: frame.preset,
1436
- projection,
1441
+ projection: schemaProjection,
1442
+ renderProjection,
1443
+ camera,
1437
1444
  scaleModel,
1438
1445
  title: String(document2.system?.title ?? document2.system?.properties.title ?? document2.system?.id ?? "WorldOrbit") || "WorldOrbit",
1439
- subtitle: `${capitalizeLabel(projection)} view - ${capitalizeLabel(layoutPreset)} layout`,
1446
+ subtitle: buildSceneSubtitle(schemaProjection, renderProjection, layoutPreset, camera),
1440
1447
  systemId,
1441
- viewMode: projection,
1448
+ viewMode: schemaProjection,
1442
1449
  layoutPreset,
1443
1450
  metadata: {
1444
1451
  format: document2.format,
1445
1452
  version: document2.version,
1446
- view: projection,
1453
+ view: schemaProjection,
1454
+ renderProjection,
1447
1455
  scale: String(document2.system?.properties.scale ?? layoutPreset),
1448
1456
  units: String(document2.system?.properties.units ?? "mixed"),
1449
- preset: frame.preset ?? "custom"
1457
+ preset: frame.preset ?? "custom",
1458
+ ...camera?.azimuth !== null ? { "camera.azimuth": String(camera?.azimuth) } : {},
1459
+ ...camera?.elevation !== null ? { "camera.elevation": String(camera?.elevation) } : {},
1460
+ ...camera?.roll !== null ? { "camera.roll": String(camera?.roll) } : {},
1461
+ ...camera?.distance !== null ? { "camera.distance": String(camera?.distance) } : {}
1450
1462
  },
1451
1463
  contentBounds,
1452
1464
  layers,
@@ -1483,21 +1495,42 @@ var WorldOrbit = (() => {
1483
1495
  return cloned;
1484
1496
  }
1485
1497
  const objectMap = new Map(cloned.map((object) => [object.id, object]));
1498
+ const referencedIds = /* @__PURE__ */ new Set([
1499
+ ...activeEvent.targetObjectId ? [activeEvent.targetObjectId] : [],
1500
+ ...activeEvent.participantObjectIds,
1501
+ ...activeEvent.positions.map((pose) => pose.objectId)
1502
+ ]);
1503
+ for (const objectId of referencedIds) {
1504
+ const object = objectMap.get(objectId);
1505
+ if (!object) {
1506
+ continue;
1507
+ }
1508
+ if (activeEvent.epoch) {
1509
+ object.epoch = activeEvent.epoch;
1510
+ }
1511
+ if (activeEvent.referencePlane) {
1512
+ object.referencePlane = activeEvent.referencePlane;
1513
+ }
1514
+ }
1486
1515
  for (const pose of activeEvent.positions) {
1487
1516
  const object = objectMap.get(pose.objectId);
1488
1517
  if (!object) {
1489
1518
  continue;
1490
1519
  }
1491
- object.placement = pose.placement ? structuredClone(pose.placement) : null;
1520
+ if (pose.placement) {
1521
+ object.placement = structuredClone(pose.placement);
1522
+ }
1492
1523
  if (pose.inner) {
1493
1524
  object.properties.inner = { ...pose.inner };
1494
- } else {
1495
- delete object.properties.inner;
1496
1525
  }
1497
1526
  if (pose.outer) {
1498
1527
  object.properties.outer = { ...pose.outer };
1499
- } else {
1500
- delete object.properties.outer;
1528
+ }
1529
+ if (pose.epoch) {
1530
+ object.epoch = pose.epoch;
1531
+ }
1532
+ if (pose.referencePlane) {
1533
+ object.referencePlane = pose.referencePlane;
1501
1534
  }
1502
1535
  }
1503
1536
  return cloned;
@@ -1538,10 +1571,59 @@ var WorldOrbit = (() => {
1538
1571
  }
1539
1572
  }
1540
1573
  function resolveProjection(document2, projection) {
1541
- if (projection === "topdown" || projection === "isometric") {
1574
+ if (projection === "topdown" || projection === "isometric" || projection === "orthographic" || projection === "perspective") {
1542
1575
  return projection;
1543
1576
  }
1544
- return String(document2.system?.properties.view ?? "topdown").toLowerCase() === "isometric" ? "isometric" : "topdown";
1577
+ const documentView = String(document2.system?.properties.view ?? "topdown").toLowerCase();
1578
+ return parseViewProjection(documentView) ?? "topdown";
1579
+ }
1580
+ function resolveRenderProjection(projection, camera) {
1581
+ switch (projection) {
1582
+ case "topdown":
1583
+ return "topdown";
1584
+ case "isometric":
1585
+ return "isometric";
1586
+ case "orthographic":
1587
+ return camera && (camera.azimuth !== null || camera.elevation !== null || camera.roll !== null) ? "isometric" : "topdown";
1588
+ case "perspective":
1589
+ return "isometric";
1590
+ }
1591
+ }
1592
+ function normalizeViewCamera(camera) {
1593
+ if (!camera) {
1594
+ return null;
1595
+ }
1596
+ const normalized = {
1597
+ azimuth: normalizeFiniteCameraValue(camera.azimuth),
1598
+ elevation: normalizeFiniteCameraValue(camera.elevation),
1599
+ roll: normalizeFiniteCameraValue(camera.roll),
1600
+ distance: normalizePositiveCameraDistance(camera.distance)
1601
+ };
1602
+ return normalized.azimuth !== null || normalized.elevation !== null || normalized.roll !== null || normalized.distance !== null ? normalized : null;
1603
+ }
1604
+ function normalizeFiniteCameraValue(value) {
1605
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1606
+ }
1607
+ function normalizePositiveCameraDistance(value) {
1608
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
1609
+ }
1610
+ function buildSceneSubtitle(projection, renderProjection, layoutPreset, camera) {
1611
+ const parts = [`${capitalizeLabel(projection)} view`, `${capitalizeLabel(layoutPreset)} layout`];
1612
+ if (projection !== renderProjection) {
1613
+ parts.push(`2D ${renderProjection} fallback`);
1614
+ }
1615
+ if (camera) {
1616
+ const cameraParts = [
1617
+ camera.azimuth !== null ? `az ${camera.azimuth}` : null,
1618
+ camera.elevation !== null ? `el ${camera.elevation}` : null,
1619
+ camera.roll !== null ? `roll ${camera.roll}` : null,
1620
+ camera.distance !== null ? `dist ${camera.distance}` : null
1621
+ ].filter(Boolean);
1622
+ if (cameraParts.length > 0) {
1623
+ parts.push(`camera ${cameraParts.join(" / ")}`);
1624
+ }
1625
+ }
1626
+ return parts.join(" - ");
1545
1627
  }
1546
1628
  function resolveScaleModel(layoutPreset, overrides) {
1547
1629
  const defaults = defaultScaleModel(layoutPreset);
@@ -1977,6 +2059,8 @@ var WorldOrbit = (() => {
1977
2059
  function createGeneratedOverviewViewpoint(document2, projection, preset) {
1978
2060
  const title = document2.system?.title ?? document2.system?.properties.title;
1979
2061
  const label = title ? `${String(title)} Overview` : "Overview";
2062
+ const camera = normalizeViewCamera(null);
2063
+ const renderProjection = resolveRenderProjection(projection, camera);
1980
2064
  return {
1981
2065
  id: "overview",
1982
2066
  label,
@@ -1985,6 +2069,8 @@ var WorldOrbit = (() => {
1985
2069
  selectedObjectId: null,
1986
2070
  eventIds: [],
1987
2071
  projection,
2072
+ renderProjection,
2073
+ camera,
1988
2074
  preset,
1989
2075
  rotationDeg: 0,
1990
2076
  scale: null,
@@ -2034,6 +2120,30 @@ var WorldOrbit = (() => {
2034
2120
  case "angle":
2035
2121
  draft.rotationDeg = parseFiniteNumber(normalizedValue) ?? draft.rotationDeg ?? 0;
2036
2122
  return;
2123
+ case "camera.azimuth":
2124
+ draft.camera = {
2125
+ ...draft.camera ?? createEmptyViewCamera(),
2126
+ azimuth: parseFiniteNumber(normalizedValue)
2127
+ };
2128
+ return;
2129
+ case "camera.elevation":
2130
+ draft.camera = {
2131
+ ...draft.camera ?? createEmptyViewCamera(),
2132
+ elevation: parseFiniteNumber(normalizedValue)
2133
+ };
2134
+ return;
2135
+ case "camera.roll":
2136
+ draft.camera = {
2137
+ ...draft.camera ?? createEmptyViewCamera(),
2138
+ roll: parseFiniteNumber(normalizedValue)
2139
+ };
2140
+ return;
2141
+ case "camera.distance":
2142
+ draft.camera = {
2143
+ ...draft.camera ?? createEmptyViewCamera(),
2144
+ distance: parsePositiveNumber(normalizedValue)
2145
+ };
2146
+ return;
2037
2147
  case "zoom":
2038
2148
  case "scale":
2039
2149
  draft.scale = parsePositiveNumber(normalizedValue);
@@ -2073,6 +2183,9 @@ var WorldOrbit = (() => {
2073
2183
  const selectedObjectId = draft.select && objectMap.has(draft.select) ? draft.select : objectId;
2074
2184
  const filter = normalizeViewpointFilter(draft.filter);
2075
2185
  const label = draft.label?.trim() || humanizeIdentifier(draft.id);
2186
+ const resolvedProjection = draft.projection ?? projection;
2187
+ const camera = normalizeViewCamera(draft.camera ?? null);
2188
+ const renderProjection = resolveRenderProjection(resolvedProjection, camera);
2076
2189
  return {
2077
2190
  id: draft.id,
2078
2191
  label,
@@ -2080,7 +2193,9 @@ var WorldOrbit = (() => {
2080
2193
  objectId,
2081
2194
  selectedObjectId,
2082
2195
  eventIds: [...new Set(draft.eventIds ?? [])],
2083
- projection: draft.projection ?? projection,
2196
+ projection: resolvedProjection,
2197
+ renderProjection,
2198
+ camera,
2084
2199
  preset: draft.preset ?? preset,
2085
2200
  rotationDeg: draft.rotationDeg ?? 0,
2086
2201
  scale: draft.scale ?? null,
@@ -2097,6 +2212,14 @@ var WorldOrbit = (() => {
2097
2212
  groupIds: []
2098
2213
  };
2099
2214
  }
2215
+ function createEmptyViewCamera() {
2216
+ return {
2217
+ azimuth: null,
2218
+ elevation: null,
2219
+ roll: null,
2220
+ distance: null
2221
+ };
2222
+ }
2100
2223
  function normalizeViewpointFilter(filter) {
2101
2224
  if (!filter) {
2102
2225
  return null;
@@ -2110,7 +2233,18 @@ var WorldOrbit = (() => {
2110
2233
  return normalized.query || normalized.objectTypes.length > 0 || normalized.tags.length > 0 || normalized.groupIds.length > 0 ? normalized : null;
2111
2234
  }
2112
2235
  function parseViewProjection(value) {
2113
- return value.toLowerCase() === "isometric" ? "isometric" : value.toLowerCase() === "topdown" ? "topdown" : null;
2236
+ switch (value.toLowerCase()) {
2237
+ case "topdown":
2238
+ return "topdown";
2239
+ case "isometric":
2240
+ return "isometric";
2241
+ case "orthographic":
2242
+ return "orthographic";
2243
+ case "perspective":
2244
+ return "perspective";
2245
+ default:
2246
+ return null;
2247
+ }
2114
2248
  }
2115
2249
  function parseRenderPreset(value) {
2116
2250
  const normalized = value.toLowerCase();
@@ -2148,7 +2282,7 @@ var WorldOrbit = (() => {
2148
2282
  }
2149
2283
  function parseViewpointGroups(value, document2, relationships, objectMap) {
2150
2284
  return splitListValue(value).map((entry) => {
2151
- if (document2.schemaVersion === "2.1" || document2.groups.some((group) => group.id === entry)) {
2285
+ if (document2.schemaVersion === "2.1" || document2.schemaVersion === "2.5" || document2.groups.some((group) => group.id === entry)) {
2152
2286
  return entry;
2153
2287
  }
2154
2288
  if (entry.startsWith("wo-") && entry.endsWith("-group")) {
@@ -2980,7 +3114,9 @@ var WorldOrbit = (() => {
2980
3114
  objectId: pose.objectId,
2981
3115
  placement: clonePlacement(pose.placement),
2982
3116
  inner: pose.inner ? { ...pose.inner } : void 0,
2983
- outer: pose.outer ? { ...pose.outer } : void 0
3117
+ outer: pose.outer ? { ...pose.outer } : void 0,
3118
+ epoch: pose.epoch ?? null,
3119
+ referencePlane: pose.referencePlane ?? null
2984
3120
  };
2985
3121
  }
2986
3122
  function clonePlacement(placement) {
@@ -2995,21 +3131,42 @@ var WorldOrbit = (() => {
2995
3131
  return;
2996
3132
  }
2997
3133
  const objectMap = new Map(objects.map((object) => [object.id, object]));
3134
+ const referencedIds = /* @__PURE__ */ new Set([
3135
+ ...event.targetObjectId ? [event.targetObjectId] : [],
3136
+ ...event.participantObjectIds,
3137
+ ...event.positions.map((pose) => pose.objectId)
3138
+ ]);
3139
+ for (const objectId of referencedIds) {
3140
+ const object = objectMap.get(objectId);
3141
+ if (!object) {
3142
+ continue;
3143
+ }
3144
+ if (event.epoch) {
3145
+ object.epoch = event.epoch;
3146
+ }
3147
+ if (event.referencePlane) {
3148
+ object.referencePlane = event.referencePlane;
3149
+ }
3150
+ }
2998
3151
  for (const pose of event.positions) {
2999
3152
  const object = objectMap.get(pose.objectId);
3000
3153
  if (!object) {
3001
3154
  continue;
3002
3155
  }
3003
- object.placement = clonePlacement(pose.placement);
3156
+ if (pose.placement) {
3157
+ object.placement = clonePlacement(pose.placement);
3158
+ }
3004
3159
  if (pose.inner) {
3005
3160
  object.properties.inner = { ...pose.inner };
3006
- } else {
3007
- delete object.properties.inner;
3008
3161
  }
3009
3162
  if (pose.outer) {
3010
3163
  object.properties.outer = { ...pose.outer };
3011
- } else {
3012
- delete object.properties.outer;
3164
+ }
3165
+ if (pose.epoch) {
3166
+ object.epoch = pose.epoch;
3167
+ }
3168
+ if (pose.referencePlane) {
3169
+ object.referencePlane = pose.referencePlane;
3013
3170
  }
3014
3171
  }
3015
3172
  }
@@ -3085,6 +3242,18 @@ var WorldOrbit = (() => {
3085
3242
  if (viewpoint.rotationDeg !== 0) {
3086
3243
  info2[`${prefix}.rotation`] = String(viewpoint.rotationDeg);
3087
3244
  }
3245
+ if (viewpoint.camera?.azimuth !== null) {
3246
+ info2[`${prefix}.camera.azimuth`] = String(viewpoint.camera?.azimuth);
3247
+ }
3248
+ if (viewpoint.camera?.elevation !== null) {
3249
+ info2[`${prefix}.camera.elevation`] = String(viewpoint.camera?.elevation);
3250
+ }
3251
+ if (viewpoint.camera?.roll !== null) {
3252
+ info2[`${prefix}.camera.roll`] = String(viewpoint.camera?.roll);
3253
+ }
3254
+ if (viewpoint.camera?.distance !== null) {
3255
+ info2[`${prefix}.camera.distance`] = String(viewpoint.camera?.distance);
3256
+ }
3088
3257
  const serializedLayers = serializeViewpointLayers(viewpoint.layers);
3089
3258
  if (serializedLayers) {
3090
3259
  info2[`${prefix}.layers`] = serializedLayers;
@@ -3328,13 +3497,13 @@ var WorldOrbit = (() => {
3328
3497
  validateRelation(relation, objectMap, diagnostics);
3329
3498
  }
3330
3499
  for (const viewpoint of document2.system?.viewpoints ?? []) {
3331
- validateViewpoint(viewpoint.filter, viewpoint.events ?? [], groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpoint.id);
3500
+ validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap);
3332
3501
  }
3333
3502
  for (const object of document2.objects) {
3334
3503
  validateObject(object, document2.system, objectMap, groupIds, diagnostics);
3335
3504
  }
3336
3505
  for (const event of document2.events) {
3337
- validateEvent(event, objectMap, diagnostics);
3506
+ validateEvent(event, document2.system, objectMap, diagnostics);
3338
3507
  }
3339
3508
  return diagnostics;
3340
3509
  }
@@ -3353,21 +3522,24 @@ var WorldOrbit = (() => {
3353
3522
  diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
3354
3523
  }
3355
3524
  }
3356
- function validateViewpoint(filter, eventRefs, groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpointId) {
3357
- if (sourceSchemaVersion === "2.1") {
3525
+ function validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap) {
3526
+ const filter = viewpoint.filter;
3527
+ if (sourceSchemaVersion === "2.1" || sourceSchemaVersion === "2.5") {
3358
3528
  if (filter) {
3359
3529
  for (const groupId of filter.groupIds) {
3360
3530
  if (!groupIds.has(groupId)) {
3361
- diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.groups`));
3531
+ diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.groups`));
3362
3532
  }
3363
3533
  }
3364
3534
  }
3365
- for (const eventId of eventRefs) {
3535
+ for (const eventId of viewpoint.events ?? []) {
3366
3536
  if (!eventIds.has(eventId)) {
3367
- diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.events`));
3537
+ diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.events`));
3368
3538
  }
3369
3539
  }
3370
3540
  }
3541
+ validateProjection(viewpoint.projection, diagnostics, `viewpoint.${viewpoint.id}.projection`, viewpoint.id);
3542
+ validateCamera(viewpoint.camera, viewpoint.projection, viewpoint.rotationDeg, diagnostics, viewpoint.id, viewpoint.focusObjectId, viewpoint.selectedObjectId, filter, objectMap);
3371
3543
  }
3372
3544
  function validateObject(object, system, objectMap, groupIds, diagnostics) {
3373
3545
  const placement = object.placement;
@@ -3380,6 +3552,12 @@ var WorldOrbit = (() => {
3380
3552
  }
3381
3553
  }
3382
3554
  }
3555
+ if (typeof object.epoch === "string" && !object.epoch.trim()) {
3556
+ diagnostics.push(warn("validate.epoch.empty", `Object "${object.id}" defines an empty epoch string.`, object.id, "epoch"));
3557
+ }
3558
+ if (typeof object.referencePlane === "string" && !object.referencePlane.trim()) {
3559
+ diagnostics.push(warn("validate.referencePlane.empty", `Object "${object.id}" defines an empty reference plane string.`, object.id, "referencePlane"));
3560
+ }
3383
3561
  if (orbitPlacement) {
3384
3562
  if (!objectMap.has(orbitPlacement.target)) {
3385
3563
  diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
@@ -3451,12 +3629,18 @@ var WorldOrbit = (() => {
3451
3629
  }
3452
3630
  }
3453
3631
  }
3454
- function validateEvent(event, objectMap, diagnostics) {
3632
+ function validateEvent(event, system, objectMap, diagnostics) {
3455
3633
  const fieldPrefix = `event.${event.id}`;
3456
3634
  const referencedIds = /* @__PURE__ */ new Set();
3457
3635
  if (!event.kind.trim()) {
3458
3636
  diagnostics.push(error("validate.event.kind.required", `Event "${event.id}" is missing a "kind" value.`, void 0, `${fieldPrefix}.kind`));
3459
3637
  }
3638
+ if (typeof event.epoch === "string" && !event.epoch.trim()) {
3639
+ diagnostics.push(warn("validate.event.epoch.empty", `Event "${event.id}" defines an empty epoch string.`, void 0, `${fieldPrefix}.epoch`));
3640
+ }
3641
+ if (typeof event.referencePlane === "string" && !event.referencePlane.trim()) {
3642
+ diagnostics.push(warn("validate.event.referencePlane.empty", `Event "${event.id}" defines an empty reference plane string.`, void 0, `${fieldPrefix}.referencePlane`));
3643
+ }
3460
3644
  if (!event.targetObjectId && event.participantObjectIds.length === 0) {
3461
3645
  diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
3462
3646
  }
@@ -3503,10 +3687,14 @@ var WorldOrbit = (() => {
3503
3687
  if (!referencedIds.has(pose.objectId)) {
3504
3688
  diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
3505
3689
  }
3506
- validateEventPose(pose, object, objectMap, diagnostics, poseFieldPrefix, event.id);
3690
+ validateEventPose(pose, object, event, system, objectMap, diagnostics, poseFieldPrefix, event.id);
3691
+ }
3692
+ const missingPoseIds = [...referencedIds].filter((objectId) => !poseIds.has(objectId));
3693
+ if (event.positions.length > 0 && missingPoseIds.length > 0) {
3694
+ diagnostics.push(warn("validate.event.positions.partial", `Event "${event.id}" leaves ${missingPoseIds.length} referenced object(s) on their base placement.`, void 0, `${fieldPrefix}.positions`));
3507
3695
  }
3508
3696
  }
3509
- function validateEventPose(pose, object, objectMap, diagnostics, fieldPrefix, eventId) {
3697
+ function validateEventPose(pose, object, event, system, objectMap, diagnostics, fieldPrefix, eventId) {
3510
3698
  const placement = pose.placement;
3511
3699
  if (!placement) {
3512
3700
  diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
@@ -3519,6 +3707,15 @@ var WorldOrbit = (() => {
3519
3707
  if (placement.distance && placement.semiMajor) {
3520
3708
  diagnostics.push(error("validate.event.pose.orbit.distanceConflict", `Event "${eventId}" pose "${pose.objectId}" cannot declare both "distance" and "semiMajor".`, void 0, `${fieldPrefix}.distance`));
3521
3709
  }
3710
+ if (placement.phase && !resolveEffectiveEpoch(system, object, event, pose)) {
3711
+ diagnostics.push(warn("validate.event.pose.phase.epochMissing", `Event "${eventId}" pose "${pose.objectId}" sets "phase" without an effective epoch.`, void 0, `${fieldPrefix}.phase`));
3712
+ }
3713
+ if (placement.inclination && !resolveEffectiveReferencePlane(system, object, event, pose)) {
3714
+ diagnostics.push(warn("validate.event.pose.inclination.referencePlaneMissing", `Event "${eventId}" pose "${pose.objectId}" sets "inclination" without an effective reference plane.`, void 0, `${fieldPrefix}.inclination`));
3715
+ }
3716
+ if (placement.period && !massInSolar(objectMap.get(placement.target)?.properties.mass)) {
3717
+ 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`));
3718
+ }
3522
3719
  return;
3523
3720
  }
3524
3721
  if (placement.mode === "surface") {
@@ -3653,6 +3850,52 @@ var WorldOrbit = (() => {
3653
3850
  return null;
3654
3851
  }
3655
3852
  }
3853
+ function validateProjection(projection, diagnostics, field, viewpointId) {
3854
+ if (projection !== "topdown" && projection !== "isometric" && projection !== "orthographic" && projection !== "perspective") {
3855
+ diagnostics.push(error("validate.viewpoint.projection.invalid", `Unknown projection "${String(projection)}" in viewpoint "${viewpointId}".`, void 0, field));
3856
+ }
3857
+ }
3858
+ function validateCamera(camera, projection, rotationDeg, diagnostics, viewpointId, focusObjectId, selectedObjectId, filter, objectMap) {
3859
+ if (!camera) {
3860
+ return;
3861
+ }
3862
+ const prefix = `viewpoint.${viewpointId}.camera`;
3863
+ for (const [key, value] of [
3864
+ ["azimuth", camera.azimuth],
3865
+ ["elevation", camera.elevation],
3866
+ ["roll", camera.roll],
3867
+ ["distance", camera.distance]
3868
+ ]) {
3869
+ if (value !== null && (!Number.isFinite(value) || key === "distance" && value <= 0)) {
3870
+ diagnostics.push(error("validate.viewpoint.camera.invalid", `Invalid camera ${key} "${String(value)}" in viewpoint "${viewpointId}".`, void 0, `${prefix}.${key}`));
3871
+ }
3872
+ }
3873
+ if (camera.distance !== null && projection !== "perspective") {
3874
+ 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`));
3875
+ }
3876
+ if (projection === "topdown" && (camera.elevation !== null || camera.roll !== null)) {
3877
+ 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));
3878
+ }
3879
+ if (projection === "isometric" && camera.elevation !== null) {
3880
+ diagnostics.push(info("validate.viewpoint.camera.isometricStored", `Camera elevation on isometric viewpoint "${viewpointId}" is preserved semantically for future 3D rendering.`, void 0, `${prefix}.elevation`));
3881
+ }
3882
+ if (camera.azimuth !== null && camera.azimuth !== 0 && rotationDeg !== 0) {
3883
+ 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`));
3884
+ }
3885
+ const hasAnchor = focusObjectId !== null && objectMap.has(focusObjectId) || selectedObjectId !== null && objectMap.has(selectedObjectId) || !!filter;
3886
+ if (!hasAnchor) {
3887
+ diagnostics.push(info("validate.viewpoint.camera.anchorMissing", `Viewpoint "${viewpointId}" stores camera settings without a focus object, selection, or filter anchor.`, void 0, prefix));
3888
+ }
3889
+ }
3890
+ function resolveEffectiveEpoch(system, object, event, pose) {
3891
+ return normalizeOptionalContextString(pose?.epoch) ?? normalizeOptionalContextString(event?.epoch) ?? normalizeOptionalContextString(object.epoch) ?? normalizeOptionalContextString(system?.epoch) ?? null;
3892
+ }
3893
+ function resolveEffectiveReferencePlane(system, object, event, pose) {
3894
+ return normalizeOptionalContextString(pose?.referencePlane) ?? normalizeOptionalContextString(event?.referencePlane) ?? normalizeOptionalContextString(object.referencePlane) ?? normalizeOptionalContextString(system?.referencePlane) ?? null;
3895
+ }
3896
+ function normalizeOptionalContextString(value) {
3897
+ return typeof value === "string" && value.trim() ? value.trim() : null;
3898
+ }
3656
3899
  function toleranceForField(object, field) {
3657
3900
  const tolerance = object.tolerances?.find((entry) => entry.field === field)?.value;
3658
3901
  if (typeof tolerance === "number") {
@@ -3761,7 +4004,9 @@ var WorldOrbit = (() => {
3761
4004
  "surface",
3762
4005
  "free",
3763
4006
  "inner",
3764
- "outer"
4007
+ "outer",
4008
+ "epoch",
4009
+ "referencePlane"
3765
4010
  ]);
3766
4011
  function parseWorldOrbitAtlas(source) {
3767
4012
  return parseAtlasSource(source);
@@ -3803,7 +4048,7 @@ var WorldOrbit = (() => {
3803
4048
  if (!sawSchemaHeader) {
3804
4049
  sourceSchemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
3805
4050
  sawSchemaHeader = true;
3806
- if (prepared.comments.length > 0 && sourceSchemaVersion !== "2.1") {
4051
+ if (prepared.comments.length > 0 && isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
3807
4052
  diagnostics.push({
3808
4053
  code: "parse.schema21.commentCompatibility",
3809
4054
  severity: "warning",
@@ -3873,11 +4118,11 @@ var WorldOrbit = (() => {
3873
4118
  return document2;
3874
4119
  }
3875
4120
  function assertDraftSchemaHeader(tokens, line) {
3876
- if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1"].includes(tokens[1].value.toLowerCase())) {
3877
- throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
4121
+ if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1", "2.5"].includes(tokens[1].value.toLowerCase())) {
4122
+ 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);
3878
4123
  }
3879
4124
  const version = tokens[1].value.toLowerCase();
3880
- return version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
4125
+ return version === "2.5" ? "2.5" : version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
3881
4126
  }
3882
4127
  function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
3883
4128
  const keyword = tokens[0]?.value.toLowerCase();
@@ -3897,6 +4142,8 @@ var WorldOrbit = (() => {
3897
4142
  return {
3898
4143
  kind: "defaults",
3899
4144
  system,
4145
+ sourceSchemaVersion,
4146
+ diagnostics,
3900
4147
  seenFields: /* @__PURE__ */ new Set()
3901
4148
  };
3902
4149
  case "atlas":
@@ -3989,6 +4236,7 @@ var WorldOrbit = (() => {
3989
4236
  preset: system.defaults.preset,
3990
4237
  zoom: null,
3991
4238
  rotationDeg: 0,
4239
+ camera: null,
3992
4240
  layers: {},
3993
4241
  filter: null
3994
4242
  };
@@ -4002,7 +4250,10 @@ var WorldOrbit = (() => {
4002
4250
  seenFields: /* @__PURE__ */ new Set(),
4003
4251
  inFilter: false,
4004
4252
  filterIndent: null,
4005
- seenFilterFields: /* @__PURE__ */ new Set()
4253
+ seenFilterFields: /* @__PURE__ */ new Set(),
4254
+ inCamera: false,
4255
+ cameraIndent: null,
4256
+ seenCameraFields: /* @__PURE__ */ new Set()
4006
4257
  };
4007
4258
  }
4008
4259
  function startAnnotationSection(tokens, line, system, annotationIds) {
@@ -4109,6 +4360,8 @@ var WorldOrbit = (() => {
4109
4360
  participantObjectIds: [],
4110
4361
  timing: null,
4111
4362
  visibility: null,
4363
+ epoch: null,
4364
+ referencePlane: null,
4112
4365
  tags: [],
4113
4366
  color: null,
4114
4367
  hidden: false,
@@ -4233,6 +4486,12 @@ var WorldOrbit = (() => {
4233
4486
  const value = joinFieldValue(tokens, line);
4234
4487
  switch (key) {
4235
4488
  case "view":
4489
+ if (isSchema25Projection(value)) {
4490
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "defaults.view", {
4491
+ line,
4492
+ column: tokens[0].column
4493
+ });
4494
+ }
4236
4495
  section.system.defaults.view = parseProjectionValue(value, line, tokens[0].column);
4237
4496
  return;
4238
4497
  case "scale":
@@ -4272,14 +4531,36 @@ var WorldOrbit = (() => {
4272
4531
  throw new WorldOrbitError(`Unknown atlas field "${tokens[0].value}"`, line, tokens[0].column);
4273
4532
  }
4274
4533
  function applyViewpointField2(section, indent, tokens, line) {
4534
+ if (section.inCamera && indent <= (section.cameraIndent ?? 0)) {
4535
+ section.inCamera = false;
4536
+ section.cameraIndent = null;
4537
+ }
4275
4538
  if (section.inFilter && indent <= (section.filterIndent ?? 0)) {
4276
4539
  section.inFilter = false;
4277
4540
  section.filterIndent = null;
4278
4541
  }
4542
+ if (section.inCamera) {
4543
+ applyViewpointCameraField(section, tokens, line);
4544
+ return;
4545
+ }
4279
4546
  if (section.inFilter) {
4280
4547
  applyViewpointFilterField(section, tokens, line);
4281
4548
  return;
4282
4549
  }
4550
+ if (tokens.length === 1 && tokens[0].value.toLowerCase() === "camera") {
4551
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
4552
+ line,
4553
+ column: tokens[0].column
4554
+ });
4555
+ if (section.seenFields.has("camera")) {
4556
+ throw new WorldOrbitError('Duplicate viewpoint field "camera"', line, tokens[0].column);
4557
+ }
4558
+ section.seenFields.add("camera");
4559
+ section.inCamera = true;
4560
+ section.cameraIndent = indent;
4561
+ section.viewpoint.camera = section.viewpoint.camera ?? createEmptyViewCamera2();
4562
+ return;
4563
+ }
4283
4564
  if (tokens.length === 1 && tokens[0].value.toLowerCase() === "filter") {
4284
4565
  if (section.seenFields.has("filter")) {
4285
4566
  throw new WorldOrbitError('Duplicate viewpoint field "filter"', line, tokens[0].column);
@@ -4305,6 +4586,12 @@ var WorldOrbit = (() => {
4305
4586
  section.viewpoint.selectedObjectId = value;
4306
4587
  return;
4307
4588
  case "projection":
4589
+ if (isSchema25Projection(value)) {
4590
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "projection", {
4591
+ line,
4592
+ column: tokens[0].column
4593
+ });
4594
+ }
4308
4595
  section.viewpoint.projection = parseProjectionValue(value, line, tokens[0].column);
4309
4596
  return;
4310
4597
  case "preset":
@@ -4316,6 +4603,13 @@ var WorldOrbit = (() => {
4316
4603
  case "rotation":
4317
4604
  section.viewpoint.rotationDeg = parseFiniteNumber2(value, line, tokens[0].column, "rotation");
4318
4605
  return;
4606
+ case "camera":
4607
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
4608
+ line,
4609
+ column: tokens[0].column
4610
+ });
4611
+ section.viewpoint.camera = parseInlineViewCamera(tokens.slice(1), line, section.viewpoint.camera);
4612
+ return;
4319
4613
  case "layers":
4320
4614
  section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line, section.sourceSchemaVersion, section.diagnostics);
4321
4615
  return;
@@ -4330,6 +4624,28 @@ var WorldOrbit = (() => {
4330
4624
  throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
4331
4625
  }
4332
4626
  }
4627
+ function applyViewpointCameraField(section, tokens, line) {
4628
+ const key = requireUniqueField(tokens, section.seenCameraFields, line);
4629
+ const value = joinFieldValue(tokens, line);
4630
+ const camera = section.viewpoint.camera ?? createEmptyViewCamera2();
4631
+ switch (key) {
4632
+ case "azimuth":
4633
+ camera.azimuth = parseFiniteNumber2(value, line, tokens[0].column, "camera.azimuth");
4634
+ break;
4635
+ case "elevation":
4636
+ camera.elevation = parseFiniteNumber2(value, line, tokens[0].column, "camera.elevation");
4637
+ break;
4638
+ case "roll":
4639
+ camera.roll = parseFiniteNumber2(value, line, tokens[0].column, "camera.roll");
4640
+ break;
4641
+ case "distance":
4642
+ camera.distance = parsePositiveNumber2(value, line, tokens[0].column, "camera.distance");
4643
+ break;
4644
+ default:
4645
+ throw new WorldOrbitError(`Unknown viewpoint camera field "${tokens[0].value}"`, line, tokens[0].column);
4646
+ }
4647
+ section.viewpoint.camera = camera;
4648
+ }
4333
4649
  function applyViewpointFilterField(section, tokens, line) {
4334
4650
  const key = requireUniqueField(tokens, section.seenFilterFields, line);
4335
4651
  const filter = section.viewpoint.filter ?? createEmptyViewpointFilter2();
@@ -4440,6 +4756,12 @@ var WorldOrbit = (() => {
4440
4756
  section.positionsIndent = null;
4441
4757
  }
4442
4758
  if (section.activePose) {
4759
+ if (tokens[0]?.value === "epoch" || tokens[0]?.value === "referencePlane") {
4760
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, `pose.${tokens[0].value}`, {
4761
+ line,
4762
+ column: tokens[0]?.column ?? 1
4763
+ });
4764
+ }
4443
4765
  section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
4444
4766
  return;
4445
4767
  }
@@ -4494,6 +4816,20 @@ var WorldOrbit = (() => {
4494
4816
  case "visibility":
4495
4817
  section.event.visibility = joinFieldValue(tokens, line);
4496
4818
  return;
4819
+ case "epoch":
4820
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.epoch", {
4821
+ line,
4822
+ column: tokens[0].column
4823
+ });
4824
+ section.event.epoch = joinFieldValue(tokens, line);
4825
+ return;
4826
+ case "referenceplane":
4827
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.referencePlane", {
4828
+ line,
4829
+ column: tokens[0].column
4830
+ });
4831
+ section.event.referencePlane = joinFieldValue(tokens, line);
4832
+ return;
4497
4833
  case "tags":
4498
4834
  section.event.tags = parseTokenList(tokens.slice(1), line, "tags");
4499
4835
  return;
@@ -4621,11 +4957,15 @@ var WorldOrbit = (() => {
4621
4957
  }
4622
4958
  function parseProjectionValue(value, line, column) {
4623
4959
  const normalized = value.toLowerCase();
4624
- if (normalized !== "topdown" && normalized !== "isometric") {
4960
+ if (normalized !== "topdown" && normalized !== "isometric" && normalized !== "orthographic" && normalized !== "perspective") {
4625
4961
  throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
4626
4962
  }
4627
4963
  return normalized;
4628
4964
  }
4965
+ function isSchema25Projection(value) {
4966
+ const normalized = value.toLowerCase();
4967
+ return normalized === "orthographic" || normalized === "perspective";
4968
+ }
4629
4969
  function parsePresetValue(value, line, column) {
4630
4970
  const normalized = value.toLowerCase();
4631
4971
  if (normalized === "diagram" || normalized === "presentation" || normalized === "atlas-card" || normalized === "markdown") {
@@ -4655,6 +4995,48 @@ var WorldOrbit = (() => {
4655
4995
  groupIds: []
4656
4996
  };
4657
4997
  }
4998
+ function createEmptyViewCamera2() {
4999
+ return {
5000
+ azimuth: null,
5001
+ elevation: null,
5002
+ roll: null,
5003
+ distance: null
5004
+ };
5005
+ }
5006
+ function parseInlineViewCamera(tokens, line, current) {
5007
+ if (tokens.length === 0 || tokens.length % 2 !== 0) {
5008
+ throw new WorldOrbitError('Field "camera" expects "<field> <value>" pairs', line, tokens[0]?.column ?? 1);
5009
+ }
5010
+ const camera = current ? { ...current } : createEmptyViewCamera2();
5011
+ const seen = /* @__PURE__ */ new Set();
5012
+ for (let index = 0; index < tokens.length; index += 2) {
5013
+ const fieldToken = tokens[index];
5014
+ const valueToken = tokens[index + 1];
5015
+ const key = fieldToken.value.toLowerCase();
5016
+ if (seen.has(key)) {
5017
+ throw new WorldOrbitError(`Duplicate viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
5018
+ }
5019
+ seen.add(key);
5020
+ const value = valueToken.value;
5021
+ switch (key) {
5022
+ case "azimuth":
5023
+ camera.azimuth = parseFiniteNumber2(value, line, fieldToken.column, "camera.azimuth");
5024
+ break;
5025
+ case "elevation":
5026
+ camera.elevation = parseFiniteNumber2(value, line, fieldToken.column, "camera.elevation");
5027
+ break;
5028
+ case "roll":
5029
+ camera.roll = parseFiniteNumber2(value, line, fieldToken.column, "camera.roll");
5030
+ break;
5031
+ case "distance":
5032
+ camera.distance = parsePositiveNumber2(value, line, fieldToken.column, "camera.distance");
5033
+ break;
5034
+ default:
5035
+ throw new WorldOrbitError(`Unknown viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
5036
+ }
5037
+ }
5038
+ return camera;
5039
+ }
4658
5040
  function parseInlineObjectFields(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
4659
5041
  const fields = [];
4660
5042
  let index = 0;
@@ -4787,7 +5169,7 @@ var WorldOrbit = (() => {
4787
5169
  object.tolerances = tolerances;
4788
5170
  if (typedBlocks && Object.keys(typedBlocks).length > 0)
4789
5171
  object.typedBlocks = typedBlocks;
4790
- if (sourceSchemaVersion !== "2.1") {
5172
+ if (isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
4791
5173
  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) {
4792
5174
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
4793
5175
  }
@@ -4803,23 +5185,25 @@ var WorldOrbit = (() => {
4803
5185
  };
4804
5186
  }
4805
5187
  function normalizeDraftEventPose(rawPose) {
4806
- const fieldMap = collectDraftFields(rawPose.fields);
5188
+ const fieldMap = collectDraftFields(rawPose.fields, "event-pose");
4807
5189
  const placement = extractPlacementFromFieldMap(fieldMap);
4808
5190
  return {
4809
5191
  objectId: rawPose.objectId,
4810
5192
  placement,
4811
5193
  inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
4812
- outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer")
5194
+ outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer"),
5195
+ epoch: parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]),
5196
+ referencePlane: parseOptionalJoinedValue(fieldMap.get("referencePlane")?.[0])
4813
5197
  };
4814
5198
  }
4815
- function collectDraftFields(fields) {
5199
+ function collectDraftFields(fields, _mode = "object") {
4816
5200
  const grouped = /* @__PURE__ */ new Map();
4817
5201
  for (const field of fields) {
4818
5202
  const spec = getDraftObjectFieldSpec(field.key);
4819
- if (!spec) {
5203
+ if (!spec && !EVENT_POSE_FIELD_KEYS.has(field.key)) {
4820
5204
  throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
4821
5205
  }
4822
- if (!spec.allowRepeat && grouped.has(field.key)) {
5206
+ if (!spec?.allowRepeat && grouped.has(field.key)) {
4823
5207
  throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
4824
5208
  }
4825
5209
  const existing = grouped.get(field.key) ?? [];
@@ -4996,7 +5380,7 @@ var WorldOrbit = (() => {
4996
5380
  }
4997
5381
  }
4998
5382
  function warnIfSchema21Feature(sourceSchemaVersion, diagnostics, featureName, location) {
4999
- if (sourceSchemaVersion === "2.1") {
5383
+ if (!isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
5000
5384
  return;
5001
5385
  }
5002
5386
  diagnostics.push({
@@ -5008,6 +5392,34 @@ var WorldOrbit = (() => {
5008
5392
  column: location.column
5009
5393
  });
5010
5394
  }
5395
+ function warnIfSchema25Feature(sourceSchemaVersion, diagnostics, featureName, location) {
5396
+ if (!isSchemaOlderThan(sourceSchemaVersion, "2.5")) {
5397
+ return;
5398
+ }
5399
+ diagnostics.push({
5400
+ code: "parse.schema25.featureCompatibility",
5401
+ severity: "warning",
5402
+ source: "parse",
5403
+ message: `Feature "${featureName}" requires schema 2.5; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
5404
+ line: location.line,
5405
+ column: location.column
5406
+ });
5407
+ }
5408
+ function isSchemaOlderThan(sourceSchemaVersion, requiredVersion) {
5409
+ return schemaVersionRank(sourceSchemaVersion) < schemaVersionRank(requiredVersion);
5410
+ }
5411
+ function schemaVersionRank(version) {
5412
+ switch (version) {
5413
+ case "2.0-draft":
5414
+ return 0;
5415
+ case "2.0":
5416
+ return 1;
5417
+ case "2.1":
5418
+ return 2;
5419
+ case "2.5":
5420
+ return 3;
5421
+ }
5422
+ }
5011
5423
  function preprocessAtlasSource(source) {
5012
5424
  const chars = [...source];
5013
5425
  const comments = [];
@@ -5095,8 +5507,9 @@ var WorldOrbit = (() => {
5095
5507
  }
5096
5508
 
5097
5509
  // packages/core/dist/load.js
5098
- var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1)?$/i;
5510
+ var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1|\.5)?$/i;
5099
5511
  var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
5512
+ var ATLAS_SCHEMA_25_PATTERN = /^schema\s+2\.5$/i;
5100
5513
  var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
5101
5514
  function detectWorldOrbitSchemaVersion(source) {
5102
5515
  for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
@@ -5110,6 +5523,9 @@ var WorldOrbit = (() => {
5110
5523
  if (ATLAS_SCHEMA_21_PATTERN.test(trimmed)) {
5111
5524
  return "2.1";
5112
5525
  }
5526
+ if (ATLAS_SCHEMA_25_PATTERN.test(trimmed)) {
5527
+ return "2.5";
5528
+ }
5113
5529
  if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
5114
5530
  return "2.0";
5115
5531
  }
@@ -5170,7 +5586,7 @@ var WorldOrbit = (() => {
5170
5586
  }
5171
5587
  function loadWorldOrbitSourceWithDiagnostics(source) {
5172
5588
  const schemaVersion = detectWorldOrbitSchemaVersion(source);
5173
- if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1") {
5589
+ if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5") {
5174
5590
  return loadAtlasSourceWithDiagnostics(source, schemaVersion);
5175
5591
  }
5176
5592
  let ast;
@@ -6244,6 +6660,7 @@ var WorldOrbit = (() => {
6244
6660
  padding: options.padding,
6245
6661
  preset: options.preset,
6246
6662
  projection: options.projection,
6663
+ camera: options.camera ? { ...options.camera } : null,
6247
6664
  scaleModel: options.scaleModel ? { ...options.scaleModel } : void 0,
6248
6665
  theme: options.theme,
6249
6666
  layers: options.layers,
@@ -6532,6 +6949,11 @@ var WorldOrbit = (() => {
6532
6949
  if (currentInput.kind !== "scene" && viewpoint.projection !== scene.projection) {
6533
6950
  nextRenderOptions.projection = viewpoint.projection;
6534
6951
  }
6952
+ if (viewpoint.camera) {
6953
+ nextRenderOptions.camera = { ...viewpoint.camera };
6954
+ } else if (renderOptions.camera) {
6955
+ nextRenderOptions.camera = null;
6956
+ }
6535
6957
  if (viewpointLayers) {
6536
6958
  nextRenderOptions.layers = viewpointLayers;
6537
6959
  }
@@ -7138,6 +7560,7 @@ var WorldOrbit = (() => {
7138
7560
  function cloneRenderOptions(renderOptions) {
7139
7561
  return {
7140
7562
  ...renderOptions,
7563
+ camera: renderOptions.camera ? { ...renderOptions.camera } : null,
7141
7564
  filter: renderOptions.filter ? { ...renderOptions.filter } : void 0,
7142
7565
  scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
7143
7566
  layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
@@ -7149,6 +7572,7 @@ var WorldOrbit = (() => {
7149
7572
  return {
7150
7573
  ...current,
7151
7574
  ...next,
7575
+ camera: next.camera !== void 0 ? next.camera ? { ...next.camera } : null : current.camera ? { ...current.camera } : null,
7152
7576
  filter: next.filter !== void 0 ? normalizeViewerFilter(next.filter) : current.filter ? { ...current.filter } : void 0,
7153
7577
  scaleModel: next.scaleModel ? {
7154
7578
  ...current.scaleModel ?? {},
@@ -7162,7 +7586,7 @@ var WorldOrbit = (() => {
7162
7586
  };
7163
7587
  }
7164
7588
  function hasSceneAffectingRenderOptions(options) {
7165
- 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;
7589
+ 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;
7166
7590
  }
7167
7591
  function resolveSourceRenderOptions2(loaded, renderOptions) {
7168
7592
  const atlasDocument = loaded.atlasDocument ?? loaded.draftDocument;
@@ -7759,6 +8183,8 @@ var WorldOrbit = (() => {
7759
8183
  }
7760
8184
  function buildInspectorSnapshot() {
7761
8185
  const activeViewer = requireViewer();
8186
+ const scene = activeViewer.getScene();
8187
+ const camera = scene.camera;
7762
8188
  return {
7763
8189
  selection: activeViewer.getSelectionDetails(),
7764
8190
  activeViewpoint: activeViewer.getActiveViewpoint(),
@@ -7766,14 +8192,21 @@ var WorldOrbit = (() => {
7766
8192
  atlasState: activeViewer.getAtlasState(),
7767
8193
  visibleObjectIds: activeViewer.getVisibleObjects().map((object) => object.objectId),
7768
8194
  scene: {
7769
- title: activeViewer.getScene().title,
7770
- projection: activeViewer.getScene().projection,
7771
- renderPreset: activeViewer.getScene().renderPreset,
7772
- groupCount: activeViewer.getScene().groups.length,
7773
- semanticGroupCount: activeViewer.getScene().semanticGroups.length,
7774
- relationCount: activeViewer.getScene().relations.length,
7775
- eventCount: activeViewer.getScene().events.length,
7776
- viewpointCount: activeViewer.getScene().viewpoints.length
8195
+ title: scene.title,
8196
+ projection: scene.projection,
8197
+ renderProjection: scene.renderProjection,
8198
+ camera: camera ? {
8199
+ azimuth: camera.azimuth,
8200
+ elevation: camera.elevation,
8201
+ roll: camera.roll,
8202
+ distance: camera.distance
8203
+ } : null,
8204
+ renderPreset: scene.renderPreset,
8205
+ groupCount: scene.groups.length,
8206
+ semanticGroupCount: scene.semanticGroups.length,
8207
+ relationCount: scene.relations.length,
8208
+ eventCount: scene.events.length,
8209
+ viewpointCount: scene.viewpoints.length
7777
8210
  }
7778
8211
  };
7779
8212
  }