worldorbit 2.5.16 → 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.
Files changed (35) hide show
  1. package/README.md +81 -15
  2. package/dist/browser/core/dist/index.js +1228 -110
  3. package/dist/browser/editor/dist/index.js +1896 -180
  4. package/dist/browser/markdown/dist/index.js +1071 -99
  5. package/dist/browser/viewer/dist/index.js +1127 -113
  6. package/dist/unpkg/core/dist/index.js +1228 -110
  7. package/dist/unpkg/editor/dist/index.js +1896 -180
  8. package/dist/unpkg/markdown/dist/index.js +1071 -99
  9. package/dist/unpkg/viewer/dist/index.js +1127 -113
  10. package/dist/unpkg/worldorbit-core.min.js +12 -12
  11. package/dist/unpkg/worldorbit-editor.min.js +295 -203
  12. package/dist/unpkg/worldorbit-markdown.min.js +66 -58
  13. package/dist/unpkg/worldorbit-viewer.min.js +84 -76
  14. package/dist/unpkg/worldorbit.js +1304 -124
  15. package/dist/unpkg/worldorbit.min.js +88 -80
  16. package/package.json +1 -1
  17. package/packages/core/dist/atlas-edit.js +75 -1
  18. package/packages/core/dist/atlas-validate.js +211 -8
  19. package/packages/core/dist/draft-parse.js +401 -22
  20. package/packages/core/dist/draft.d.ts +5 -2
  21. package/packages/core/dist/draft.js +103 -8
  22. package/packages/core/dist/format.js +99 -6
  23. package/packages/core/dist/load.js +9 -2
  24. package/packages/core/dist/normalize.js +1 -0
  25. package/packages/core/dist/scene.js +400 -64
  26. package/packages/core/dist/types.d.ts +60 -4
  27. package/packages/editor/dist/editor.js +702 -65
  28. package/packages/editor/dist/types.d.ts +3 -1
  29. package/packages/viewer/dist/atlas-state.js +11 -2
  30. package/packages/viewer/dist/atlas-viewer.js +19 -7
  31. package/packages/viewer/dist/render.js +31 -2
  32. package/packages/viewer/dist/theme.js +1 -0
  33. package/packages/viewer/dist/tooltip.js +9 -0
  34. package/packages/viewer/dist/types.d.ts +12 -2
  35. package/packages/viewer/dist/viewer.js +28 -1
@@ -647,6 +647,7 @@ var WorldOrbit = (() => {
647
647
  system,
648
648
  groups: [],
649
649
  relations: [],
650
+ events: [],
650
651
  objects
651
652
  };
652
653
  }
@@ -1100,12 +1101,16 @@ var WorldOrbit = (() => {
1100
1101
  const height = frame.height;
1101
1102
  const padding = frame.padding;
1102
1103
  const layoutPreset = resolveLayoutPreset(document2);
1103
- const projection = resolveProjection(document2, options.projection);
1104
+ const schemaProjection = resolveProjection(document2, options.projection);
1105
+ const camera = normalizeViewCamera(options.camera ?? null);
1106
+ const renderProjection = resolveRenderProjection(schemaProjection, camera);
1104
1107
  const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
1105
1108
  const spacingFactor = layoutPresetSpacing(layoutPreset);
1106
1109
  const systemId = document2.system?.id ?? null;
1107
- const objectMap = new Map(document2.objects.map((object) => [object.id, object]));
1108
- const relationships = buildSceneRelationships(document2.objects, objectMap);
1110
+ const activeEventId = options.activeEventId ?? null;
1111
+ const effectiveObjects = createEffectiveObjects(document2.objects, document2.events ?? [], activeEventId);
1112
+ const objectMap = new Map(effectiveObjects.map((object) => [object.id, object]));
1113
+ const relationships = buildSceneRelationships(effectiveObjects, objectMap);
1109
1114
  const positions = /* @__PURE__ */ new Map();
1110
1115
  const orbitDrafts = [];
1111
1116
  const leaderDrafts = [];
@@ -1114,7 +1119,7 @@ var WorldOrbit = (() => {
1114
1119
  const atObjects = [];
1115
1120
  const surfaceChildren = /* @__PURE__ */ new Map();
1116
1121
  const orbitChildren = /* @__PURE__ */ new Map();
1117
- for (const object of document2.objects) {
1122
+ for (const object of effectiveObjects) {
1118
1123
  const placement = object.placement;
1119
1124
  if (!placement) {
1120
1125
  rootObjects.push(object);
@@ -1141,7 +1146,7 @@ var WorldOrbit = (() => {
1141
1146
  surfaceChildren,
1142
1147
  objectMap,
1143
1148
  spacingFactor,
1144
- projection,
1149
+ projection: renderProjection,
1145
1150
  scaleModel
1146
1151
  };
1147
1152
  const primaryRoot = rootObjects.find((object) => object.type === "star") ?? rootObjects[0] ?? null;
@@ -1153,7 +1158,7 @@ var WorldOrbit = (() => {
1153
1158
  const rootRingRadius = Math.min(width, height) * 0.28 * spacingFactor * scaleModel.orbitDistanceMultiplier;
1154
1159
  secondaryRoots.forEach((object, index) => {
1155
1160
  const angle = angleForIndex(index, secondaryRoots.length, -Math.PI / 2);
1156
- const offset = projectPolarOffset(angle, rootRingRadius, projection, 1);
1161
+ const offset = projectPolarOffset(angle, rootRingRadius, renderProjection, 1);
1157
1162
  placeObject(object, centerX + offset.x, centerY + offset.y, 0, positions, orbitDrafts, leaderDrafts, context);
1158
1163
  });
1159
1164
  }
@@ -1209,38 +1214,48 @@ var WorldOrbit = (() => {
1209
1214
  const objects = [...positions.values()].map((position) => createSceneObject(position, scaleModel, relationships));
1210
1215
  const orbitVisuals = orbitDrafts.map((draft) => createOrbitVisual(draft, relationships.groupIds.get(draft.object.id) ?? null));
1211
1216
  const leaders = leaderDrafts.map((draft) => createLeaderLine(draft));
1212
- const labels = createSceneLabels(objects, height, scaleModel.labelMultiplier);
1217
+ const labels = createSceneLabels(objects, width, height, scaleModel.labelMultiplier);
1213
1218
  const relations = createSceneRelations(document2, objects);
1214
- const layers = createSceneLayers(orbitVisuals, relations, leaders, objects, labels);
1215
- const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships);
1219
+ const events = createSceneEvents(document2.events ?? [], objects, activeEventId);
1220
+ const layers = createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels);
1221
+ const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, scaleModel.labelMultiplier);
1216
1222
  const semanticGroups = createSceneSemanticGroups(document2, objects);
1217
- const viewpoints = createSceneViewpoints(document2, projection, frame.preset, relationships, objectMap);
1218
- const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels);
1223
+ const viewpoints = createSceneViewpoints(document2, schemaProjection, frame.preset, relationships, objectMap);
1224
+ const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, scaleModel.labelMultiplier);
1219
1225
  return {
1220
1226
  width,
1221
1227
  height,
1222
1228
  padding,
1223
1229
  renderPreset: frame.preset,
1224
- projection,
1230
+ projection: schemaProjection,
1231
+ renderProjection,
1232
+ camera,
1225
1233
  scaleModel,
1226
1234
  title: String(document2.system?.title ?? document2.system?.properties.title ?? document2.system?.id ?? "WorldOrbit") || "WorldOrbit",
1227
- subtitle: `${capitalizeLabel(projection)} view - ${capitalizeLabel(layoutPreset)} layout`,
1235
+ subtitle: buildSceneSubtitle(schemaProjection, renderProjection, layoutPreset, camera),
1228
1236
  systemId,
1229
- viewMode: projection,
1237
+ viewMode: schemaProjection,
1230
1238
  layoutPreset,
1231
1239
  metadata: {
1232
1240
  format: document2.format,
1233
1241
  version: document2.version,
1234
- view: projection,
1242
+ view: schemaProjection,
1243
+ renderProjection,
1235
1244
  scale: String(document2.system?.properties.scale ?? layoutPreset),
1236
1245
  units: String(document2.system?.properties.units ?? "mixed"),
1237
- preset: frame.preset ?? "custom"
1246
+ preset: frame.preset ?? "custom",
1247
+ ...camera?.azimuth !== null ? { "camera.azimuth": String(camera?.azimuth) } : {},
1248
+ ...camera?.elevation !== null ? { "camera.elevation": String(camera?.elevation) } : {},
1249
+ ...camera?.roll !== null ? { "camera.roll": String(camera?.roll) } : {},
1250
+ ...camera?.distance !== null ? { "camera.distance": String(camera?.distance) } : {}
1238
1251
  },
1239
1252
  contentBounds,
1240
1253
  layers,
1241
1254
  groups,
1242
1255
  semanticGroups,
1243
1256
  viewpoints,
1257
+ events,
1258
+ activeEventId,
1244
1259
  objects,
1245
1260
  orbitVisuals,
1246
1261
  relations,
@@ -1259,6 +1274,56 @@ var WorldOrbit = (() => {
1259
1274
  y: center.y + dx * sin + dy * cos
1260
1275
  };
1261
1276
  }
1277
+ function createEffectiveObjects(objects, events, activeEventId) {
1278
+ const cloned = objects.map((object) => structuredClone(object));
1279
+ if (!activeEventId) {
1280
+ return cloned;
1281
+ }
1282
+ const activeEvent = events.find((event) => event.id === activeEventId);
1283
+ if (!activeEvent) {
1284
+ return cloned;
1285
+ }
1286
+ const objectMap = new Map(cloned.map((object) => [object.id, object]));
1287
+ const referencedIds = /* @__PURE__ */ new Set([
1288
+ ...activeEvent.targetObjectId ? [activeEvent.targetObjectId] : [],
1289
+ ...activeEvent.participantObjectIds,
1290
+ ...activeEvent.positions.map((pose) => pose.objectId)
1291
+ ]);
1292
+ for (const objectId of referencedIds) {
1293
+ const object = objectMap.get(objectId);
1294
+ if (!object) {
1295
+ continue;
1296
+ }
1297
+ if (activeEvent.epoch) {
1298
+ object.epoch = activeEvent.epoch;
1299
+ }
1300
+ if (activeEvent.referencePlane) {
1301
+ object.referencePlane = activeEvent.referencePlane;
1302
+ }
1303
+ }
1304
+ for (const pose of activeEvent.positions) {
1305
+ const object = objectMap.get(pose.objectId);
1306
+ if (!object) {
1307
+ continue;
1308
+ }
1309
+ if (pose.placement) {
1310
+ object.placement = structuredClone(pose.placement);
1311
+ }
1312
+ if (pose.inner) {
1313
+ object.properties.inner = { ...pose.inner };
1314
+ }
1315
+ if (pose.outer) {
1316
+ object.properties.outer = { ...pose.outer };
1317
+ }
1318
+ if (pose.epoch) {
1319
+ object.epoch = pose.epoch;
1320
+ }
1321
+ if (pose.referencePlane) {
1322
+ object.referencePlane = pose.referencePlane;
1323
+ }
1324
+ }
1325
+ return cloned;
1326
+ }
1262
1327
  function resolveLayoutPreset(document2) {
1263
1328
  const rawScale = String(document2.system?.properties.scale ?? "balanced").toLowerCase();
1264
1329
  switch (rawScale) {
@@ -1295,10 +1360,59 @@ var WorldOrbit = (() => {
1295
1360
  }
1296
1361
  }
1297
1362
  function resolveProjection(document2, projection) {
1298
- if (projection === "topdown" || projection === "isometric") {
1363
+ if (projection === "topdown" || projection === "isometric" || projection === "orthographic" || projection === "perspective") {
1299
1364
  return projection;
1300
1365
  }
1301
- return String(document2.system?.properties.view ?? "topdown").toLowerCase() === "isometric" ? "isometric" : "topdown";
1366
+ const documentView = String(document2.system?.properties.view ?? "topdown").toLowerCase();
1367
+ return parseViewProjection(documentView) ?? "topdown";
1368
+ }
1369
+ function resolveRenderProjection(projection, camera) {
1370
+ switch (projection) {
1371
+ case "topdown":
1372
+ return "topdown";
1373
+ case "isometric":
1374
+ return "isometric";
1375
+ case "orthographic":
1376
+ return camera && (camera.azimuth !== null || camera.elevation !== null || camera.roll !== null) ? "isometric" : "topdown";
1377
+ case "perspective":
1378
+ return "isometric";
1379
+ }
1380
+ }
1381
+ function normalizeViewCamera(camera) {
1382
+ if (!camera) {
1383
+ return null;
1384
+ }
1385
+ const normalized = {
1386
+ azimuth: normalizeFiniteCameraValue(camera.azimuth),
1387
+ elevation: normalizeFiniteCameraValue(camera.elevation),
1388
+ roll: normalizeFiniteCameraValue(camera.roll),
1389
+ distance: normalizePositiveCameraDistance(camera.distance)
1390
+ };
1391
+ return normalized.azimuth !== null || normalized.elevation !== null || normalized.roll !== null || normalized.distance !== null ? normalized : null;
1392
+ }
1393
+ function normalizeFiniteCameraValue(value) {
1394
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1395
+ }
1396
+ function normalizePositiveCameraDistance(value) {
1397
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
1398
+ }
1399
+ function buildSceneSubtitle(projection, renderProjection, layoutPreset, camera) {
1400
+ const parts = [`${capitalizeLabel(projection)} view`, `${capitalizeLabel(layoutPreset)} layout`];
1401
+ if (projection !== renderProjection) {
1402
+ parts.push(`2D ${renderProjection} fallback`);
1403
+ }
1404
+ if (camera) {
1405
+ const cameraParts = [
1406
+ camera.azimuth !== null ? `az ${camera.azimuth}` : null,
1407
+ camera.elevation !== null ? `el ${camera.elevation}` : null,
1408
+ camera.roll !== null ? `roll ${camera.roll}` : null,
1409
+ camera.distance !== null ? `dist ${camera.distance}` : null
1410
+ ].filter(Boolean);
1411
+ if (cameraParts.length > 0) {
1412
+ parts.push(`camera ${cameraParts.join(" / ")}`);
1413
+ }
1414
+ }
1415
+ return parts.join(" - ");
1302
1416
  }
1303
1417
  function resolveScaleModel(layoutPreset, overrides) {
1304
1418
  const defaults = defaultScaleModel(layoutPreset);
@@ -1414,24 +1528,14 @@ var WorldOrbit = (() => {
1414
1528
  hidden: draft.object.properties.hidden === true
1415
1529
  };
1416
1530
  }
1417
- function createSceneLabels(objects, sceneHeight, labelMultiplier) {
1531
+ function createSceneLabels(objects, sceneWidth, sceneHeight, labelMultiplier) {
1418
1532
  const labels = [];
1419
1533
  const occupied = [];
1420
- const visibleObjects = [...objects].filter((object) => !object.hidden && object.object.renderHints?.renderLabel !== false).sort((left, right) => left.sortKey - right.sortKey);
1534
+ const objectMap = new Map(objects.map((object) => [object.objectId, object]));
1535
+ const visibleObjects = [...objects].filter((object) => !object.hidden && object.object.renderHints?.renderLabel !== false).sort(compareLabelPlacementOrder);
1421
1536
  for (const object of visibleObjects) {
1422
- const direction = object.y > sceneHeight * 0.62 ? -1 : 1;
1423
- const labelHalfWidth = estimateLabelHalfWidth(object, labelMultiplier);
1424
- let labelY = object.y + direction * (object.radius + 18 * labelMultiplier);
1425
- let secondaryY = labelY + direction * (16 * labelMultiplier);
1426
- let bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
1427
- let attempts = 0;
1428
- while (occupied.some((entry) => rectsOverlap(entry, bounds)) && attempts < 10) {
1429
- labelY += direction * 14 * labelMultiplier;
1430
- secondaryY += direction * 14 * labelMultiplier;
1431
- bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
1432
- attempts += 1;
1433
- }
1434
- occupied.push(bounds);
1537
+ const placement = selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) ?? createLabelPlacement(object, defaultVerticalDirection(object, objectMap.get(object.parentId ?? "") ?? null, sceneHeight), 0, labelMultiplier);
1538
+ occupied.push(createLabelRect(object, placement, labelMultiplier));
1435
1539
  labels.push({
1436
1540
  renderId: `${object.renderId}-label`,
1437
1541
  objectId: object.objectId,
@@ -1440,17 +1544,128 @@ var WorldOrbit = (() => {
1440
1544
  semanticGroupIds: [...object.semanticGroupIds],
1441
1545
  label: object.label,
1442
1546
  secondaryLabel: object.secondaryLabel,
1443
- x: object.x,
1444
- y: labelY,
1445
- secondaryY,
1446
- textAnchor: "middle",
1447
- direction: direction < 0 ? "above" : "below",
1547
+ x: placement.x,
1548
+ y: placement.labelY,
1549
+ secondaryY: placement.secondaryY,
1550
+ textAnchor: placement.textAnchor,
1551
+ direction: placement.direction,
1448
1552
  hidden: object.hidden
1449
1553
  });
1450
1554
  }
1451
1555
  return labels;
1452
1556
  }
1453
- function createSceneLayers(orbitVisuals, relations, leaders, objects, labels) {
1557
+ function compareLabelPlacementOrder(left, right) {
1558
+ const priorityDiff = labelPlacementPriority(left) - labelPlacementPriority(right);
1559
+ if (priorityDiff !== 0) {
1560
+ return priorityDiff;
1561
+ }
1562
+ const renderPriorityDiff = (right.object.renderHints?.renderPriority ?? 0) - (left.object.renderHints?.renderPriority ?? 0);
1563
+ if (renderPriorityDiff !== 0) {
1564
+ return renderPriorityDiff;
1565
+ }
1566
+ return left.sortKey - right.sortKey;
1567
+ }
1568
+ function labelPlacementPriority(object) {
1569
+ switch (object.object.type) {
1570
+ case "star":
1571
+ return 0;
1572
+ case "planet":
1573
+ return 1;
1574
+ case "moon":
1575
+ return 2;
1576
+ case "belt":
1577
+ case "ring":
1578
+ return 3;
1579
+ case "asteroid":
1580
+ case "comet":
1581
+ return 4;
1582
+ case "structure":
1583
+ case "phenomenon":
1584
+ return 5;
1585
+ }
1586
+ }
1587
+ function selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) {
1588
+ for (const direction of preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight)) {
1589
+ const maxAttempts = direction === "left" || direction === "right" ? 4 : 6;
1590
+ for (let attempt = 0; attempt <= maxAttempts; attempt += 1) {
1591
+ const placement = createLabelPlacement(object, direction, attempt, labelMultiplier);
1592
+ const rect = createLabelRect(object, placement, labelMultiplier);
1593
+ if (!occupied.some((entry) => rectsOverlap(entry, rect))) {
1594
+ return placement;
1595
+ }
1596
+ }
1597
+ }
1598
+ return null;
1599
+ }
1600
+ function preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight) {
1601
+ const parent = object.parentId ? objectMap.get(object.parentId) ?? null : null;
1602
+ const vertical = defaultVerticalDirection(object, parent, sceneHeight);
1603
+ const oppositeVertical = vertical === "below" ? "above" : "below";
1604
+ const horizontal = defaultHorizontalDirection(object, parent, sceneWidth);
1605
+ const oppositeHorizontal = horizontal === "right" ? "left" : "right";
1606
+ const preferHorizontal = object.object.type === "structure" || object.object.type === "phenomenon" || object.object.placement?.mode === "at" || object.object.placement?.mode === "surface" || object.object.placement?.mode === "free";
1607
+ return preferHorizontal ? [horizontal, vertical, oppositeHorizontal, oppositeVertical] : [vertical, horizontal, oppositeVertical, oppositeHorizontal];
1608
+ }
1609
+ function defaultVerticalDirection(object, parent, sceneHeight) {
1610
+ if (parent && Math.abs(object.y - parent.y) > 6) {
1611
+ return object.y >= parent.y ? "below" : "above";
1612
+ }
1613
+ return object.y > sceneHeight * 0.62 ? "above" : "below";
1614
+ }
1615
+ function defaultHorizontalDirection(object, parent, sceneWidth) {
1616
+ if (parent && Math.abs(object.x - parent.x) > 6) {
1617
+ return object.x >= parent.x ? "right" : "left";
1618
+ }
1619
+ return object.x >= sceneWidth / 2 ? "right" : "left";
1620
+ }
1621
+ function createLabelPlacement(object, direction, attempt, labelMultiplier) {
1622
+ const step = 14 * labelMultiplier;
1623
+ switch (direction) {
1624
+ case "above": {
1625
+ const labelY = object.y - (object.radius + 18 * labelMultiplier + attempt * step);
1626
+ return {
1627
+ x: object.x,
1628
+ labelY,
1629
+ secondaryY: labelY - 16 * labelMultiplier,
1630
+ textAnchor: "middle",
1631
+ direction
1632
+ };
1633
+ }
1634
+ case "below": {
1635
+ const labelY = object.y + object.radius + 18 * labelMultiplier + attempt * step;
1636
+ return {
1637
+ x: object.x,
1638
+ labelY,
1639
+ secondaryY: labelY + 16 * labelMultiplier,
1640
+ textAnchor: "middle",
1641
+ direction
1642
+ };
1643
+ }
1644
+ case "left": {
1645
+ const x = object.x - (object.visualRadius + 16 * labelMultiplier + attempt * step);
1646
+ const labelY = object.y - 4 * labelMultiplier;
1647
+ return {
1648
+ x,
1649
+ labelY,
1650
+ secondaryY: labelY + 16 * labelMultiplier,
1651
+ textAnchor: "end",
1652
+ direction
1653
+ };
1654
+ }
1655
+ case "right": {
1656
+ const x = object.x + object.visualRadius + 16 * labelMultiplier + attempt * step;
1657
+ const labelY = object.y - 4 * labelMultiplier;
1658
+ return {
1659
+ x,
1660
+ labelY,
1661
+ secondaryY: labelY + 16 * labelMultiplier,
1662
+ textAnchor: "start",
1663
+ direction
1664
+ };
1665
+ }
1666
+ }
1667
+ }
1668
+ function createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels) {
1454
1669
  const backOrbitIds = orbitVisuals.filter((visual) => !visual.hidden && Boolean(visual.backArcPath)).map((visual) => visual.renderId);
1455
1670
  const frontOrbitIds = orbitVisuals.filter((visual) => !visual.hidden).map((visual) => visual.renderId);
1456
1671
  return [
@@ -1465,6 +1680,10 @@ var WorldOrbit = (() => {
1465
1680
  id: "relations",
1466
1681
  renderIds: relations.filter((relation) => !relation.hidden).map((relation) => relation.renderId)
1467
1682
  },
1683
+ {
1684
+ id: "events",
1685
+ renderIds: events.filter((event) => !event.hidden).map((event) => event.renderId)
1686
+ },
1468
1687
  {
1469
1688
  id: "objects",
1470
1689
  renderIds: objects.filter((object) => !object.hidden).map((object) => object.renderId)
@@ -1476,7 +1695,7 @@ var WorldOrbit = (() => {
1476
1695
  { id: "metadata", renderIds: ["wo-title", "wo-subtitle", "wo-meta"] }
1477
1696
  ];
1478
1697
  }
1479
- function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships) {
1698
+ function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, labelMultiplier) {
1480
1699
  const groups = /* @__PURE__ */ new Map();
1481
1700
  const ensureGroup = (groupId) => {
1482
1701
  if (!groupId) {
@@ -1525,7 +1744,7 @@ var WorldOrbit = (() => {
1525
1744
  }
1526
1745
  }
1527
1746
  for (const group of groups.values()) {
1528
- group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels);
1747
+ group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier);
1529
1748
  }
1530
1749
  return [...groups.values()].sort((left, right) => left.label.localeCompare(right.label));
1531
1750
  }
@@ -1559,6 +1778,29 @@ var WorldOrbit = (() => {
1559
1778
  };
1560
1779
  }).sort((left, right) => left.relation.id.localeCompare(right.relation.id));
1561
1780
  }
1781
+ function createSceneEvents(events, objects, activeEventId) {
1782
+ const objectMap = new Map(objects.map((object) => [object.objectId, object]));
1783
+ return events.map((event) => {
1784
+ const objectIds = [.../* @__PURE__ */ new Set([
1785
+ ...event.targetObjectId ? [event.targetObjectId] : [],
1786
+ ...event.participantObjectIds
1787
+ ])];
1788
+ const positions = objectIds.map((objectId) => objectMap.get(objectId)).filter(Boolean);
1789
+ const centroidX = positions.length > 0 ? positions.reduce((sum, object) => sum + object.x, 0) / positions.length : 0;
1790
+ const centroidY = positions.length > 0 ? positions.reduce((sum, object) => sum + object.y, 0) / positions.length : 0;
1791
+ return {
1792
+ renderId: `${createRenderId(event.id)}-event`,
1793
+ eventId: event.id,
1794
+ event,
1795
+ objectIds,
1796
+ participantIds: [...event.participantObjectIds],
1797
+ targetObjectId: event.targetObjectId,
1798
+ x: centroidX,
1799
+ y: centroidY,
1800
+ hidden: event.hidden || positions.length === 0 || positions.every((object) => object.hidden) || activeEventId !== null && event.id !== activeEventId
1801
+ };
1802
+ }).sort((left, right) => left.event.id.localeCompare(right.event.id));
1803
+ }
1562
1804
  function createSceneViewpoints(document2, projection, preset, relationships, objectMap) {
1563
1805
  const generatedOverview = createGeneratedOverviewViewpoint(document2, projection, preset);
1564
1806
  const drafts = /* @__PURE__ */ new Map();
@@ -1606,13 +1848,18 @@ var WorldOrbit = (() => {
1606
1848
  function createGeneratedOverviewViewpoint(document2, projection, preset) {
1607
1849
  const title = document2.system?.title ?? document2.system?.properties.title;
1608
1850
  const label = title ? `${String(title)} Overview` : "Overview";
1851
+ const camera = normalizeViewCamera(null);
1852
+ const renderProjection = resolveRenderProjection(projection, camera);
1609
1853
  return {
1610
1854
  id: "overview",
1611
1855
  label,
1612
1856
  summary: "Fit the whole system with the current atlas defaults.",
1613
1857
  objectId: null,
1614
1858
  selectedObjectId: null,
1859
+ eventIds: [],
1615
1860
  projection,
1861
+ renderProjection,
1862
+ camera,
1616
1863
  preset,
1617
1864
  rotationDeg: 0,
1618
1865
  scale: null,
@@ -1648,6 +1895,9 @@ var WorldOrbit = (() => {
1648
1895
  draft.select = normalizedValue;
1649
1896
  }
1650
1897
  return;
1898
+ case "events":
1899
+ draft.eventIds = splitListValue(normalizedValue);
1900
+ return;
1651
1901
  case "projection":
1652
1902
  case "view":
1653
1903
  draft.projection = parseViewProjection(normalizedValue) ?? projection;
@@ -1659,6 +1909,30 @@ var WorldOrbit = (() => {
1659
1909
  case "angle":
1660
1910
  draft.rotationDeg = parseFiniteNumber(normalizedValue) ?? draft.rotationDeg ?? 0;
1661
1911
  return;
1912
+ case "camera.azimuth":
1913
+ draft.camera = {
1914
+ ...draft.camera ?? createEmptyViewCamera(),
1915
+ azimuth: parseFiniteNumber(normalizedValue)
1916
+ };
1917
+ return;
1918
+ case "camera.elevation":
1919
+ draft.camera = {
1920
+ ...draft.camera ?? createEmptyViewCamera(),
1921
+ elevation: parseFiniteNumber(normalizedValue)
1922
+ };
1923
+ return;
1924
+ case "camera.roll":
1925
+ draft.camera = {
1926
+ ...draft.camera ?? createEmptyViewCamera(),
1927
+ roll: parseFiniteNumber(normalizedValue)
1928
+ };
1929
+ return;
1930
+ case "camera.distance":
1931
+ draft.camera = {
1932
+ ...draft.camera ?? createEmptyViewCamera(),
1933
+ distance: parsePositiveNumber(normalizedValue)
1934
+ };
1935
+ return;
1662
1936
  case "zoom":
1663
1937
  case "scale":
1664
1938
  draft.scale = parsePositiveNumber(normalizedValue);
@@ -1698,13 +1972,19 @@ var WorldOrbit = (() => {
1698
1972
  const selectedObjectId = draft.select && objectMap.has(draft.select) ? draft.select : objectId;
1699
1973
  const filter = normalizeViewpointFilter(draft.filter);
1700
1974
  const label = draft.label?.trim() || humanizeIdentifier(draft.id);
1975
+ const resolvedProjection = draft.projection ?? projection;
1976
+ const camera = normalizeViewCamera(draft.camera ?? null);
1977
+ const renderProjection = resolveRenderProjection(resolvedProjection, camera);
1701
1978
  return {
1702
1979
  id: draft.id,
1703
1980
  label,
1704
1981
  summary: draft.summary?.trim() || createViewpointSummary(label, objectId, filter),
1705
1982
  objectId,
1706
1983
  selectedObjectId,
1707
- projection: draft.projection ?? projection,
1984
+ eventIds: [...new Set(draft.eventIds ?? [])],
1985
+ projection: resolvedProjection,
1986
+ renderProjection,
1987
+ camera,
1708
1988
  preset: draft.preset ?? preset,
1709
1989
  rotationDeg: draft.rotationDeg ?? 0,
1710
1990
  scale: draft.scale ?? null,
@@ -1721,6 +2001,14 @@ var WorldOrbit = (() => {
1721
2001
  groupIds: []
1722
2002
  };
1723
2003
  }
2004
+ function createEmptyViewCamera() {
2005
+ return {
2006
+ azimuth: null,
2007
+ elevation: null,
2008
+ roll: null,
2009
+ distance: null
2010
+ };
2011
+ }
1724
2012
  function normalizeViewpointFilter(filter) {
1725
2013
  if (!filter) {
1726
2014
  return null;
@@ -1734,7 +2022,18 @@ var WorldOrbit = (() => {
1734
2022
  return normalized.query || normalized.objectTypes.length > 0 || normalized.tags.length > 0 || normalized.groupIds.length > 0 ? normalized : null;
1735
2023
  }
1736
2024
  function parseViewProjection(value) {
1737
- return value.toLowerCase() === "isometric" ? "isometric" : value.toLowerCase() === "topdown" ? "topdown" : null;
2025
+ switch (value.toLowerCase()) {
2026
+ case "topdown":
2027
+ return "topdown";
2028
+ case "isometric":
2029
+ return "isometric";
2030
+ case "orthographic":
2031
+ return "orthographic";
2032
+ case "perspective":
2033
+ return "perspective";
2034
+ default:
2035
+ return null;
2036
+ }
1738
2037
  }
1739
2038
  function parseRenderPreset(value) {
1740
2039
  const normalized = value.toLowerCase();
@@ -1761,7 +2060,7 @@ var WorldOrbit = (() => {
1761
2060
  next["orbits-front"] = enabled;
1762
2061
  continue;
1763
2062
  }
1764
- if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
2063
+ if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "events" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
1765
2064
  next[rawLayer] = enabled;
1766
2065
  }
1767
2066
  }
@@ -1772,7 +2071,7 @@ var WorldOrbit = (() => {
1772
2071
  }
1773
2072
  function parseViewpointGroups(value, document2, relationships, objectMap) {
1774
2073
  return splitListValue(value).map((entry) => {
1775
- if (document2.schemaVersion === "2.1" || document2.groups.some((group) => group.id === entry)) {
2074
+ if (document2.schemaVersion === "2.1" || document2.schemaVersion === "2.5" || document2.groups.some((group) => group.id === entry)) {
1776
2075
  return entry;
1777
2076
  }
1778
2077
  if (entry.startsWith("wo-") && entry.endsWith("-group")) {
@@ -1809,7 +2108,7 @@ var WorldOrbit = (() => {
1809
2108
  }
1810
2109
  return parts.join(" - ");
1811
2110
  }
1812
- function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels) {
2111
+ function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, labelMultiplier) {
1813
2112
  let minX = Number.POSITIVE_INFINITY;
1814
2113
  let minY = Number.POSITIVE_INFINITY;
1815
2114
  let maxX = Number.NEGATIVE_INFINITY;
@@ -1839,7 +2138,7 @@ var WorldOrbit = (() => {
1839
2138
  for (const label of labels) {
1840
2139
  if (label.hidden)
1841
2140
  continue;
1842
- includeLabelBounds(label, include);
2141
+ includeLabelBounds(label, include, labelMultiplier);
1843
2142
  }
1844
2143
  if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
1845
2144
  return createBounds(0, 0, width, height);
@@ -1877,13 +2176,10 @@ var WorldOrbit = (() => {
1877
2176
  include(object.x - object.visualRadius - 24, object.y - object.visualRadius - 16);
1878
2177
  include(object.x + object.visualRadius + 24, object.y + object.visualRadius + 36);
1879
2178
  }
1880
- function includeLabelBounds(label, include) {
1881
- const labelScale = 1;
1882
- const labelHalfWidth = estimateLabelHalfWidthFromText(label.label, label.secondaryLabel, labelScale);
1883
- include(label.x - labelHalfWidth, label.y - 18);
1884
- include(label.x + labelHalfWidth, label.y + 8);
1885
- include(label.x - labelHalfWidth, label.secondaryY - 14);
1886
- include(label.x + labelHalfWidth, label.secondaryY + 8);
2179
+ function includeLabelBounds(label, include, labelMultiplier) {
2180
+ const bounds = createLabelRectFromText(label.x, label.y, label.secondaryY, label.textAnchor, label.direction, label.label, label.secondaryLabel, labelMultiplier);
2181
+ include(bounds.left, bounds.top);
2182
+ include(bounds.right, bounds.bottom);
1887
2183
  }
1888
2184
  function placeObject(object, x, y, depth, positions, orbitDrafts, leaderDrafts, context) {
1889
2185
  if (positions.has(object.id)) {
@@ -2273,7 +2569,7 @@ var WorldOrbit = (() => {
2273
2569
  return null;
2274
2570
  }
2275
2571
  }
2276
- function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels) {
2572
+ function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier) {
2277
2573
  let minX = Number.POSITIVE_INFINITY;
2278
2574
  let minY = Number.POSITIVE_INFINITY;
2279
2575
  let maxX = Number.NEGATIVE_INFINITY;
@@ -2302,7 +2598,7 @@ var WorldOrbit = (() => {
2302
2598
  }
2303
2599
  for (const label of labels) {
2304
2600
  if (!label.hidden && group.labelIds.includes(label.objectId)) {
2305
- includeLabelBounds(label, include);
2601
+ includeLabelBounds(label, include, labelMultiplier);
2306
2602
  }
2307
2603
  }
2308
2604
  if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
@@ -2327,12 +2623,28 @@ var WorldOrbit = (() => {
2327
2623
  }
2328
2624
  return current.id;
2329
2625
  }
2330
- function createLabelRect(x, labelY, secondaryY, labelHalfWidth, direction) {
2626
+ function createLabelRect(object, placement, labelMultiplier) {
2627
+ return createLabelRectFromText(placement.x, placement.labelY, placement.secondaryY, placement.textAnchor, placement.direction, object.label, object.secondaryLabel, labelMultiplier);
2628
+ }
2629
+ function createLabelRectFromText(x, labelY, secondaryY, textAnchor, direction, label, secondaryLabel, labelMultiplier) {
2630
+ const labelHalfWidth = estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier);
2631
+ const labelWidth = labelHalfWidth * 2;
2632
+ const topPadding = direction === "above" ? 18 : 12;
2633
+ const bottomPadding = direction === "above" ? 8 : 12;
2634
+ let left = x - labelHalfWidth;
2635
+ let right = x + labelHalfWidth;
2636
+ if (textAnchor === "start") {
2637
+ left = x;
2638
+ right = x + labelWidth;
2639
+ } else if (textAnchor === "end") {
2640
+ left = x - labelWidth;
2641
+ right = x;
2642
+ }
2331
2643
  return {
2332
- left: x - labelHalfWidth,
2333
- right: x + labelHalfWidth,
2334
- top: Math.min(labelY, secondaryY) - (direction < 0 ? 18 : 12),
2335
- bottom: Math.max(labelY, secondaryY) + (direction < 0 ? 8 : 12)
2644
+ left,
2645
+ right,
2646
+ top: Math.min(labelY, secondaryY) - topPadding,
2647
+ bottom: Math.max(labelY, secondaryY) + bottomPadding
2336
2648
  };
2337
2649
  }
2338
2650
  function rectsOverlap(left, right) {
@@ -2519,11 +2831,6 @@ var WorldOrbit = (() => {
2519
2831
  function customColorFor(value) {
2520
2832
  return typeof value === "string" && value.trim() ? value : void 0;
2521
2833
  }
2522
- function estimateLabelHalfWidth(object, labelMultiplier) {
2523
- const primaryWidth = object.label.length * 4.6 * labelMultiplier + 18;
2524
- const secondaryWidth = object.secondaryLabel.length * 3.9 * labelMultiplier + 18;
2525
- return Math.max(primaryWidth, secondaryWidth, object.visualRadius + 18);
2526
- }
2527
2834
  function estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier) {
2528
2835
  const primaryWidth = label.length * 4.6 * labelMultiplier + 18;
2529
2836
  const secondaryWidth = secondaryLabel.length * 3.9 * labelMultiplier + 18;
@@ -2557,12 +2864,13 @@ var WorldOrbit = (() => {
2557
2864
  }
2558
2865
  return {
2559
2866
  format: "worldorbit",
2560
- version: "2.0",
2561
- schemaVersion: "2.0",
2867
+ version: "2.5",
2868
+ schemaVersion: "2.5",
2562
2869
  sourceVersion: document2.version,
2563
2870
  system,
2564
2871
  groups: structuredClone(document2.groups ?? []),
2565
2872
  relations: structuredClone(document2.relations ?? []),
2873
+ events: structuredClone(document2.events ?? []),
2566
2874
  objects: document2.objects.map(cloneWorldOrbitObject),
2567
2875
  diagnostics
2568
2876
  };
@@ -2570,7 +2878,7 @@ var WorldOrbit = (() => {
2570
2878
  function upgradeDocumentToDraftV2(document2, options = {}) {
2571
2879
  return convertAtlasDocumentToLegacyDraft(upgradeDocumentToV2(document2, options));
2572
2880
  }
2573
- function materializeAtlasDocument(document2) {
2881
+ function materializeAtlasDocument(document2, options = {}) {
2574
2882
  const system = document2.system ? {
2575
2883
  type: "system",
2576
2884
  id: document2.system.id,
@@ -2581,6 +2889,8 @@ var WorldOrbit = (() => {
2581
2889
  properties: materializeDraftSystemProperties(document2.system),
2582
2890
  info: materializeDraftSystemInfo(document2.system)
2583
2891
  } : null;
2892
+ const objects = document2.objects.map(cloneWorldOrbitObject);
2893
+ applyEventPoseOverrides(objects, document2.events ?? [], options.activeEventId ?? null);
2584
2894
  return {
2585
2895
  format: "worldorbit",
2586
2896
  version: "1.0",
@@ -2588,7 +2898,8 @@ var WorldOrbit = (() => {
2588
2898
  system,
2589
2899
  groups: structuredClone(document2.groups ?? []),
2590
2900
  relations: structuredClone(document2.relations ?? []),
2591
- objects: document2.objects.map(cloneWorldOrbitObject)
2901
+ events: document2.events.map(cloneWorldOrbitEvent),
2902
+ objects
2592
2903
  };
2593
2904
  }
2594
2905
  function materializeDraftDocument(document2) {
@@ -2613,8 +2924,9 @@ var WorldOrbit = (() => {
2613
2924
  };
2614
2925
  }
2615
2926
  function createDraftDefaults(document2, preset, projection) {
2927
+ const rawView = typeof document2.system?.properties.view === "string" ? document2.system.properties.view.toLowerCase() : null;
2616
2928
  return {
2617
- view: typeof document2.system?.properties.view === "string" && document2.system.properties.view.toLowerCase() === "topdown" ? "topdown" : projection,
2929
+ view: rawView === "topdown" || rawView === "isometric" || rawView === "orthographic" || rawView === "perspective" ? rawView : projection,
2618
2930
  scale: typeof document2.system?.properties.scale === "string" ? document2.system.properties.scale : null,
2619
2931
  units: typeof document2.system?.properties.units === "string" ? document2.system.properties.units : null,
2620
2932
  preset,
@@ -2716,10 +3028,12 @@ var WorldOrbit = (() => {
2716
3028
  summary: viewpoint.summary,
2717
3029
  focusObjectId: viewpoint.objectId,
2718
3030
  selectedObjectId: viewpoint.selectedObjectId,
3031
+ events: [...viewpoint.eventIds],
2719
3032
  projection: viewpoint.projection,
2720
3033
  preset: viewpoint.preset,
2721
3034
  zoom: viewpoint.scale,
2722
3035
  rotationDeg: viewpoint.rotationDeg,
3036
+ camera: viewpoint.camera ? { ...viewpoint.camera } : null,
2723
3037
  layers: { ...viewpoint.layers },
2724
3038
  filter: viewpoint.filter ? {
2725
3039
  query: viewpoint.filter.query,
@@ -2748,6 +3062,75 @@ var WorldOrbit = (() => {
2748
3062
  info: { ...object.info }
2749
3063
  };
2750
3064
  }
3065
+ function cloneWorldOrbitEvent(event) {
3066
+ return {
3067
+ ...event,
3068
+ participantObjectIds: [...event.participantObjectIds],
3069
+ tags: [...event.tags],
3070
+ positions: event.positions.map(cloneWorldOrbitEventPose)
3071
+ };
3072
+ }
3073
+ function cloneWorldOrbitEventPose(pose) {
3074
+ return {
3075
+ objectId: pose.objectId,
3076
+ placement: clonePlacement(pose.placement),
3077
+ inner: pose.inner ? { ...pose.inner } : void 0,
3078
+ outer: pose.outer ? { ...pose.outer } : void 0,
3079
+ epoch: pose.epoch ?? null,
3080
+ referencePlane: pose.referencePlane ?? null
3081
+ };
3082
+ }
3083
+ function clonePlacement(placement) {
3084
+ return placement ? structuredClone(placement) : null;
3085
+ }
3086
+ function applyEventPoseOverrides(objects, events, activeEventId) {
3087
+ if (!activeEventId) {
3088
+ return;
3089
+ }
3090
+ const event = events.find((entry) => entry.id === activeEventId);
3091
+ if (!event) {
3092
+ return;
3093
+ }
3094
+ const objectMap = new Map(objects.map((object) => [object.id, object]));
3095
+ const referencedIds = /* @__PURE__ */ new Set([
3096
+ ...event.targetObjectId ? [event.targetObjectId] : [],
3097
+ ...event.participantObjectIds,
3098
+ ...event.positions.map((pose) => pose.objectId)
3099
+ ]);
3100
+ for (const objectId of referencedIds) {
3101
+ const object = objectMap.get(objectId);
3102
+ if (!object) {
3103
+ continue;
3104
+ }
3105
+ if (event.epoch) {
3106
+ object.epoch = event.epoch;
3107
+ }
3108
+ if (event.referencePlane) {
3109
+ object.referencePlane = event.referencePlane;
3110
+ }
3111
+ }
3112
+ for (const pose of event.positions) {
3113
+ const object = objectMap.get(pose.objectId);
3114
+ if (!object) {
3115
+ continue;
3116
+ }
3117
+ if (pose.placement) {
3118
+ object.placement = clonePlacement(pose.placement);
3119
+ }
3120
+ if (pose.inner) {
3121
+ object.properties.inner = { ...pose.inner };
3122
+ }
3123
+ if (pose.outer) {
3124
+ object.properties.outer = { ...pose.outer };
3125
+ }
3126
+ if (pose.epoch) {
3127
+ object.epoch = pose.epoch;
3128
+ }
3129
+ if (pose.referencePlane) {
3130
+ object.referencePlane = pose.referencePlane;
3131
+ }
3132
+ }
3133
+ }
2751
3134
  function cloneProperties(properties) {
2752
3135
  const next = {};
2753
3136
  for (const [key, value] of Object.entries(properties)) {
@@ -2829,6 +3212,18 @@ var WorldOrbit = (() => {
2829
3212
  if (viewpoint.rotationDeg !== 0) {
2830
3213
  info2[`${prefix}.rotation`] = String(viewpoint.rotationDeg);
2831
3214
  }
3215
+ if (viewpoint.camera?.azimuth !== null) {
3216
+ info2[`${prefix}.camera.azimuth`] = String(viewpoint.camera?.azimuth);
3217
+ }
3218
+ if (viewpoint.camera?.elevation !== null) {
3219
+ info2[`${prefix}.camera.elevation`] = String(viewpoint.camera?.elevation);
3220
+ }
3221
+ if (viewpoint.camera?.roll !== null) {
3222
+ info2[`${prefix}.camera.roll`] = String(viewpoint.camera?.roll);
3223
+ }
3224
+ if (viewpoint.camera?.distance !== null) {
3225
+ info2[`${prefix}.camera.distance`] = String(viewpoint.camera?.distance);
3226
+ }
2832
3227
  const serializedLayers = serializeViewpointLayers(viewpoint.layers);
2833
3228
  if (serializedLayers) {
2834
3229
  info2[`${prefix}.layers`] = serializedLayers;
@@ -2845,6 +3240,9 @@ var WorldOrbit = (() => {
2845
3240
  if ((viewpoint.filter?.groupIds.length ?? 0) > 0) {
2846
3241
  info2[`${prefix}.groups`] = viewpoint.filter?.groupIds.join(" ") ?? "";
2847
3242
  }
3243
+ if (viewpoint.events.length > 0) {
3244
+ info2[`${prefix}.events`] = viewpoint.events.join(" ");
3245
+ }
2848
3246
  }
2849
3247
  for (const annotation of system.annotations) {
2850
3248
  const prefix = `annotation.${annotation.id}`;
@@ -2869,7 +3267,7 @@ var WorldOrbit = (() => {
2869
3267
  if (orbitFront !== void 0 || orbitBack !== void 0) {
2870
3268
  tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
2871
3269
  }
2872
- for (const key of ["background", "guides", "relations", "objects", "labels", "metadata"]) {
3270
+ for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
2873
3271
  if (layers[key] !== void 0) {
2874
3272
  tokens.push(layers[key] ? key : `-${key}`);
2875
3273
  }
@@ -2922,26 +3320,26 @@ var WorldOrbit = (() => {
2922
3320
  ];
2923
3321
  function formatDocument(document2, options = {}) {
2924
3322
  const schema = options.schema ?? "auto";
2925
- const useDraft = schema === "2.0" || schema === "2.1" || schema === "2.0-draft" || document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.0-draft";
3323
+ const useDraft = schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.0-draft" || document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" || document2.version === "2.0-draft";
2926
3324
  if (useDraft) {
2927
3325
  if (schema === "2.0-draft") {
2928
- const legacyDraftDocument = document2.version === "2.0-draft" ? document2 : document2.version === "2.0" || document2.version === "2.1" ? {
3326
+ const legacyDraftDocument = document2.version === "2.0-draft" ? document2 : document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" ? {
2929
3327
  ...document2,
2930
3328
  version: "2.0-draft",
2931
3329
  schemaVersion: "2.0-draft"
2932
3330
  } : upgradeDocumentToDraftV2(document2);
2933
3331
  return formatDraftDocument(legacyDraftDocument);
2934
3332
  }
2935
- const atlasDocument = document2.version === "2.0" || document2.version === "2.1" ? document2 : document2.version === "2.0-draft" ? {
3333
+ const atlasDocument = document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" ? document2 : document2.version === "2.0-draft" ? {
2936
3334
  ...document2,
2937
3335
  version: "2.0",
2938
3336
  schemaVersion: "2.0"
2939
3337
  } : upgradeDocumentToV2(document2);
2940
- if (schema === "2.1" && atlasDocument.version !== "2.1") {
3338
+ if ((schema === "2.0" || schema === "2.1" || schema === "2.5") && atlasDocument.version !== schema) {
2941
3339
  return formatAtlasDocument({
2942
3340
  ...atlasDocument,
2943
- version: "2.1",
2944
- schemaVersion: "2.1"
3341
+ version: schema,
3342
+ schemaVersion: schema
2945
3343
  });
2946
3344
  }
2947
3345
  return formatAtlasDocument(atlasDocument);
@@ -2973,6 +3371,10 @@ var WorldOrbit = (() => {
2973
3371
  lines.push("");
2974
3372
  lines.push(...formatAtlasRelation(relation));
2975
3373
  }
3374
+ for (const event of [...document2.events].sort(compareIdLike)) {
3375
+ lines.push("");
3376
+ lines.push(...formatAtlasEvent(event));
3377
+ }
2976
3378
  const sortedObjects = [...document2.objects].sort(compareObjects);
2977
3379
  if (sortedObjects.length > 0 && lines.at(-1) !== "") {
2978
3380
  lines.push("");
@@ -3003,6 +3405,10 @@ var WorldOrbit = (() => {
3003
3405
  lines.push("");
3004
3406
  lines.push(...formatAtlasRelation(relation));
3005
3407
  }
3408
+ for (const event of [...legacy.events].sort(compareIdLike)) {
3409
+ lines.push("");
3410
+ lines.push(...formatAtlasEvent(event));
3411
+ }
3006
3412
  const sortedObjects = [...legacy.objects].sort(compareObjects);
3007
3413
  if (sortedObjects.length > 0 && lines.at(-1) !== "") {
3008
3414
  lines.push("");
@@ -3210,10 +3616,28 @@ var WorldOrbit = (() => {
3210
3616
  if (viewpoint.rotationDeg !== 0) {
3211
3617
  lines.push(` rotation ${viewpoint.rotationDeg}`);
3212
3618
  }
3619
+ if (viewpoint.camera && hasCameraValues(viewpoint.camera)) {
3620
+ lines.push(" camera");
3621
+ if (viewpoint.camera.azimuth !== null) {
3622
+ lines.push(` azimuth ${viewpoint.camera.azimuth}`);
3623
+ }
3624
+ if (viewpoint.camera.elevation !== null) {
3625
+ lines.push(` elevation ${viewpoint.camera.elevation}`);
3626
+ }
3627
+ if (viewpoint.camera.roll !== null) {
3628
+ lines.push(` roll ${viewpoint.camera.roll}`);
3629
+ }
3630
+ if (viewpoint.camera.distance !== null) {
3631
+ lines.push(` distance ${viewpoint.camera.distance}`);
3632
+ }
3633
+ }
3213
3634
  const layerTokens = formatDraftLayers(viewpoint.layers);
3214
3635
  if (layerTokens.length > 0) {
3215
3636
  lines.push(` layers ${layerTokens.join(" ")}`);
3216
3637
  }
3638
+ if (viewpoint.events.length > 0) {
3639
+ lines.push(` events ${viewpoint.events.join(" ")}`);
3640
+ }
3217
3641
  if (viewpoint.filter) {
3218
3642
  lines.push(" filter");
3219
3643
  if (viewpoint.filter.query) {
@@ -3286,6 +3710,65 @@ var WorldOrbit = (() => {
3286
3710
  }
3287
3711
  return lines;
3288
3712
  }
3713
+ function formatAtlasEvent(event) {
3714
+ const lines = [`event ${event.id}`, ` kind ${quoteIfNeeded(event.kind)}`];
3715
+ if (event.label) {
3716
+ lines.push(` label ${quoteIfNeeded(event.label)}`);
3717
+ }
3718
+ if (event.summary) {
3719
+ lines.push(` summary ${quoteIfNeeded(event.summary)}`);
3720
+ }
3721
+ if (event.targetObjectId) {
3722
+ lines.push(` target ${event.targetObjectId}`);
3723
+ }
3724
+ if (event.participantObjectIds.length > 0) {
3725
+ lines.push(` participants ${event.participantObjectIds.join(" ")}`);
3726
+ }
3727
+ if (event.timing) {
3728
+ lines.push(` timing ${quoteIfNeeded(event.timing)}`);
3729
+ }
3730
+ if (event.visibility) {
3731
+ lines.push(` visibility ${quoteIfNeeded(event.visibility)}`);
3732
+ }
3733
+ if (event.epoch) {
3734
+ lines.push(` epoch ${quoteIfNeeded(event.epoch)}`);
3735
+ }
3736
+ if (event.referencePlane) {
3737
+ lines.push(` referencePlane ${quoteIfNeeded(event.referencePlane)}`);
3738
+ }
3739
+ if (event.tags.length > 0) {
3740
+ lines.push(` tags ${event.tags.map(quoteIfNeeded).join(" ")}`);
3741
+ }
3742
+ if (event.color) {
3743
+ lines.push(` color ${quoteIfNeeded(event.color)}`);
3744
+ }
3745
+ if (event.hidden) {
3746
+ lines.push(" hidden true");
3747
+ }
3748
+ if (event.positions.length > 0) {
3749
+ lines.push("");
3750
+ lines.push(" positions");
3751
+ for (const pose of [...event.positions].sort(comparePoseObjectId)) {
3752
+ lines.push(` pose ${pose.objectId}`);
3753
+ for (const fieldLine of formatEventPoseFields(pose)) {
3754
+ lines.push(` ${fieldLine}`);
3755
+ }
3756
+ }
3757
+ }
3758
+ return lines;
3759
+ }
3760
+ function formatEventPoseFields(pose) {
3761
+ return [
3762
+ ...formatPlacement(pose.placement),
3763
+ ...pose.epoch ? [`epoch ${quoteIfNeeded(pose.epoch)}`] : [],
3764
+ ...pose.referencePlane ? [`referencePlane ${quoteIfNeeded(pose.referencePlane)}`] : [],
3765
+ ...formatOptionalUnit("inner", pose.inner),
3766
+ ...formatOptionalUnit("outer", pose.outer)
3767
+ ];
3768
+ }
3769
+ function hasCameraValues(camera) {
3770
+ return camera.azimuth !== null || camera.elevation !== null || camera.roll !== null || camera.distance !== null;
3771
+ }
3289
3772
  function formatValue(value) {
3290
3773
  if (Array.isArray(value)) {
3291
3774
  return value.map((item) => quoteIfNeeded(item)).join(" ");
@@ -3327,7 +3810,7 @@ var WorldOrbit = (() => {
3327
3810
  if (orbitFront !== void 0 || orbitBack !== void 0) {
3328
3811
  tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
3329
3812
  }
3330
- for (const key of ["background", "guides", "relations", "objects", "labels", "metadata"]) {
3813
+ for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
3331
3814
  if (layers[key] !== void 0) {
3332
3815
  tokens.push(layers[key] ? key : `-${key}`);
3333
3816
  }
@@ -3355,6 +3838,9 @@ var WorldOrbit = (() => {
3355
3838
  function compareIdLike(left, right) {
3356
3839
  return left.id.localeCompare(right.id);
3357
3840
  }
3841
+ function comparePoseObjectId(left, right) {
3842
+ return left.objectId.localeCompare(right.objectId);
3843
+ }
3358
3844
  function objectTypeIndex(objectType) {
3359
3845
  switch (objectType) {
3360
3846
  case "star":
@@ -3550,6 +4036,7 @@ var WorldOrbit = (() => {
3550
4036
  const diagnostics = [];
3551
4037
  const objectMap = new Map(document2.objects.map((object) => [object.id, object]));
3552
4038
  const groupIds = new Set(document2.groups.map((group) => group.id));
4039
+ const eventIds = new Set(document2.events.map((event) => event.id));
3553
4040
  if (!document2.system) {
3554
4041
  diagnostics.push(error("validate.system.required", "Atlas documents must declare exactly one system."));
3555
4042
  }
@@ -3559,6 +4046,7 @@ var WorldOrbit = (() => {
3559
4046
  ["viewpoint", document2.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []],
3560
4047
  ["annotation", document2.system?.annotations.map((annotation) => annotation.id) ?? []],
3561
4048
  ["relation", document2.relations.map((relation) => relation.id)],
4049
+ ["event", document2.events.map((event) => event.id)],
3562
4050
  ["object", document2.objects.map((object) => object.id)]
3563
4051
  ]) {
3564
4052
  for (const id of ids) {
@@ -3574,11 +4062,14 @@ var WorldOrbit = (() => {
3574
4062
  validateRelation(relation, objectMap, diagnostics);
3575
4063
  }
3576
4064
  for (const viewpoint of document2.system?.viewpoints ?? []) {
3577
- validateViewpointFilter(viewpoint.filter, groupIds, sourceSchemaVersion, diagnostics, viewpoint.id);
4065
+ validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap);
3578
4066
  }
3579
4067
  for (const object of document2.objects) {
3580
4068
  validateObject(object, document2.system, objectMap, groupIds, diagnostics);
3581
4069
  }
4070
+ for (const event of document2.events) {
4071
+ validateEvent(event, document2.system, objectMap, diagnostics);
4072
+ }
3582
4073
  return diagnostics;
3583
4074
  }
3584
4075
  function validateRelation(relation, objectMap, diagnostics) {
@@ -3596,15 +4087,24 @@ var WorldOrbit = (() => {
3596
4087
  diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
3597
4088
  }
3598
4089
  }
3599
- function validateViewpointFilter(filter, groupIds, sourceSchemaVersion, diagnostics, viewpointId) {
3600
- if (!filter || sourceSchemaVersion !== "2.1") {
3601
- return;
3602
- }
3603
- for (const groupId of filter.groupIds) {
3604
- if (!groupIds.has(groupId)) {
3605
- diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpointId}".`));
4090
+ function validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap) {
4091
+ const filter = viewpoint.filter;
4092
+ if (sourceSchemaVersion === "2.1" || sourceSchemaVersion === "2.5") {
4093
+ if (filter) {
4094
+ for (const groupId of filter.groupIds) {
4095
+ if (!groupIds.has(groupId)) {
4096
+ diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.groups`));
4097
+ }
4098
+ }
4099
+ }
4100
+ for (const eventId of viewpoint.events ?? []) {
4101
+ if (!eventIds.has(eventId)) {
4102
+ diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.events`));
4103
+ }
3606
4104
  }
3607
4105
  }
4106
+ validateProjection(viewpoint.projection, diagnostics, `viewpoint.${viewpoint.id}.projection`, viewpoint.id);
4107
+ validateCamera(viewpoint.camera, viewpoint.projection, viewpoint.rotationDeg, diagnostics, viewpoint.id, viewpoint.focusObjectId, viewpoint.selectedObjectId, filter, objectMap);
3608
4108
  }
3609
4109
  function validateObject(object, system, objectMap, groupIds, diagnostics) {
3610
4110
  const placement = object.placement;
@@ -3617,6 +4117,12 @@ var WorldOrbit = (() => {
3617
4117
  }
3618
4118
  }
3619
4119
  }
4120
+ if (typeof object.epoch === "string" && !object.epoch.trim()) {
4121
+ diagnostics.push(warn("validate.epoch.empty", `Object "${object.id}" defines an empty epoch string.`, object.id, "epoch"));
4122
+ }
4123
+ if (typeof object.referencePlane === "string" && !object.referencePlane.trim()) {
4124
+ diagnostics.push(warn("validate.referencePlane.empty", `Object "${object.id}" defines an empty reference plane string.`, object.id, "referencePlane"));
4125
+ }
3620
4126
  if (orbitPlacement) {
3621
4127
  if (!objectMap.has(orbitPlacement.target)) {
3622
4128
  diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
@@ -3688,6 +4194,122 @@ var WorldOrbit = (() => {
3688
4194
  }
3689
4195
  }
3690
4196
  }
4197
+ function validateEvent(event, system, objectMap, diagnostics) {
4198
+ const fieldPrefix = `event.${event.id}`;
4199
+ const referencedIds = /* @__PURE__ */ new Set();
4200
+ if (!event.kind.trim()) {
4201
+ diagnostics.push(error("validate.event.kind.required", `Event "${event.id}" is missing a "kind" value.`, void 0, `${fieldPrefix}.kind`));
4202
+ }
4203
+ if (typeof event.epoch === "string" && !event.epoch.trim()) {
4204
+ diagnostics.push(warn("validate.event.epoch.empty", `Event "${event.id}" defines an empty epoch string.`, void 0, `${fieldPrefix}.epoch`));
4205
+ }
4206
+ if (typeof event.referencePlane === "string" && !event.referencePlane.trim()) {
4207
+ diagnostics.push(warn("validate.event.referencePlane.empty", `Event "${event.id}" defines an empty reference plane string.`, void 0, `${fieldPrefix}.referencePlane`));
4208
+ }
4209
+ if (!event.targetObjectId && event.participantObjectIds.length === 0) {
4210
+ diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
4211
+ }
4212
+ if (event.targetObjectId) {
4213
+ referencedIds.add(event.targetObjectId);
4214
+ if (!objectMap.has(event.targetObjectId)) {
4215
+ diagnostics.push(error("validate.event.target.unknown", `Unknown event target "${event.targetObjectId}" on "${event.id}".`, void 0, `${fieldPrefix}.target`));
4216
+ }
4217
+ }
4218
+ const seenParticipants = /* @__PURE__ */ new Set();
4219
+ for (const participantId of event.participantObjectIds) {
4220
+ referencedIds.add(participantId);
4221
+ if (seenParticipants.has(participantId)) {
4222
+ diagnostics.push(warn("validate.event.participants.duplicate", `Event "${event.id}" repeats participant "${participantId}".`, void 0, `${fieldPrefix}.participants`));
4223
+ continue;
4224
+ }
4225
+ seenParticipants.add(participantId);
4226
+ if (!objectMap.has(participantId)) {
4227
+ diagnostics.push(error("validate.event.participants.unknown", `Unknown event participant "${participantId}" on "${event.id}".`, void 0, `${fieldPrefix}.participants`));
4228
+ }
4229
+ }
4230
+ if (event.targetObjectId && event.participantObjectIds.length > 0 && !event.participantObjectIds.includes(event.targetObjectId)) {
4231
+ diagnostics.push(warn("validate.event.target.notParticipant", `Event "${event.id}" defines a target outside its participants list.`, void 0, `${fieldPrefix}.target`));
4232
+ }
4233
+ if (event.positions.length === 0) {
4234
+ diagnostics.push(warn("validate.event.positions.missing", `Event "${event.id}" has no positions block and cannot drive a scene snapshot.`, void 0, `${fieldPrefix}.positions`));
4235
+ }
4236
+ if (/(?:^|[-_])(solar-eclipse|lunar-eclipse|transit|occultation)(?:$|[-_])/.test(event.kind) && referencedIds.size < 3) {
4237
+ diagnostics.push(warn("validate.event.kind.participants", `Event "${event.id}" looks like an eclipse or transit but references fewer than three bodies.`, void 0, `${fieldPrefix}.participants`));
4238
+ }
4239
+ const poseIds = /* @__PURE__ */ new Set();
4240
+ for (const pose of event.positions) {
4241
+ const poseFieldPrefix = `${fieldPrefix}.pose.${pose.objectId}`;
4242
+ if (poseIds.has(pose.objectId)) {
4243
+ diagnostics.push(error("validate.event.pose.duplicate", `Event "${event.id}" defines "${pose.objectId}" more than once in positions.`, void 0, poseFieldPrefix));
4244
+ continue;
4245
+ }
4246
+ poseIds.add(pose.objectId);
4247
+ const object = objectMap.get(pose.objectId);
4248
+ if (!object) {
4249
+ diagnostics.push(error("validate.event.pose.object.unknown", `Unknown event pose object "${pose.objectId}" on "${event.id}".`, void 0, poseFieldPrefix));
4250
+ continue;
4251
+ }
4252
+ if (!referencedIds.has(pose.objectId)) {
4253
+ diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
4254
+ }
4255
+ validateEventPose(pose, object, event, system, objectMap, diagnostics, poseFieldPrefix, event.id);
4256
+ }
4257
+ const missingPoseIds = [...referencedIds].filter((objectId) => !poseIds.has(objectId));
4258
+ if (event.positions.length > 0 && missingPoseIds.length > 0) {
4259
+ diagnostics.push(warn("validate.event.positions.partial", `Event "${event.id}" leaves ${missingPoseIds.length} referenced object(s) on their base placement.`, void 0, `${fieldPrefix}.positions`));
4260
+ }
4261
+ }
4262
+ function validateEventPose(pose, object, event, system, objectMap, diagnostics, fieldPrefix, eventId) {
4263
+ const placement = pose.placement;
4264
+ if (!placement) {
4265
+ diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
4266
+ return;
4267
+ }
4268
+ if (placement.mode === "orbit") {
4269
+ if (!objectMap.has(placement.target)) {
4270
+ diagnostics.push(error("validate.event.pose.orbit.target.unknown", `Unknown event orbit target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.orbit`));
4271
+ }
4272
+ if (placement.distance && placement.semiMajor) {
4273
+ diagnostics.push(error("validate.event.pose.orbit.distanceConflict", `Event "${eventId}" pose "${pose.objectId}" cannot declare both "distance" and "semiMajor".`, void 0, `${fieldPrefix}.distance`));
4274
+ }
4275
+ if (placement.phase && !resolveEffectiveEpoch(system, object, event, pose)) {
4276
+ diagnostics.push(warn("validate.event.pose.phase.epochMissing", `Event "${eventId}" pose "${pose.objectId}" sets "phase" without an effective epoch.`, void 0, `${fieldPrefix}.phase`));
4277
+ }
4278
+ if (placement.inclination && !resolveEffectiveReferencePlane(system, object, event, pose)) {
4279
+ diagnostics.push(warn("validate.event.pose.inclination.referencePlaneMissing", `Event "${eventId}" pose "${pose.objectId}" sets "inclination" without an effective reference plane.`, void 0, `${fieldPrefix}.inclination`));
4280
+ }
4281
+ if (placement.period && !massInSolar(objectMap.get(placement.target)?.properties.mass)) {
4282
+ diagnostics.push(warn("validate.event.pose.period.massMissing", `Event "${eventId}" pose "${pose.objectId}" sets "period" but its central mass cannot be derived.`, void 0, `${fieldPrefix}.period`));
4283
+ }
4284
+ return;
4285
+ }
4286
+ if (placement.mode === "surface") {
4287
+ const target = objectMap.get(placement.target);
4288
+ if (!target) {
4289
+ diagnostics.push(error("validate.event.pose.surface.target.unknown", `Unknown event surface target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.surface`));
4290
+ } else if (!SURFACE_TARGET_TYPES2.has(target.type)) {
4291
+ diagnostics.push(error("validate.event.pose.surface.target.invalid", `Event surface target "${placement.target}" on "${eventId}:${pose.objectId}" is not surface-capable.`, void 0, `${fieldPrefix}.surface`));
4292
+ }
4293
+ return;
4294
+ }
4295
+ if (placement.mode === "at") {
4296
+ if (object.type !== "structure" && object.type !== "phenomenon") {
4297
+ diagnostics.push(error("validate.event.pose.at.objectType", `Only structures and phenomena may use "at" placement in events; found "${object.type}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
4298
+ }
4299
+ const reference = placement.reference;
4300
+ if (reference.kind === "named" && !objectMap.has(reference.name)) {
4301
+ diagnostics.push(error("validate.event.pose.at.target.unknown", `Unknown event at-reference target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
4302
+ } else if (reference.kind === "anchor" && !objectMap.has(reference.objectId)) {
4303
+ diagnostics.push(error("validate.event.pose.anchor.target.unknown", `Unknown event anchor target "${reference.objectId}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
4304
+ } else if (reference.kind === "lagrange") {
4305
+ if (!objectMap.has(reference.primary)) {
4306
+ diagnostics.push(error("validate.event.pose.lagrange.primary.unknown", `Unknown event Lagrange target "${reference.primary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
4307
+ } else if (reference.secondary && !objectMap.has(reference.secondary)) {
4308
+ diagnostics.push(error("validate.event.pose.lagrange.secondary.unknown", `Unknown event Lagrange target "${reference.secondary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
4309
+ }
4310
+ }
4311
+ }
4312
+ }
3691
4313
  function validateAtTarget(object, objectMap, diagnostics) {
3692
4314
  const reference = object.placement?.mode === "at" ? object.placement.reference : null;
3693
4315
  if (!reference) {
@@ -3793,6 +4415,52 @@ var WorldOrbit = (() => {
3793
4415
  return null;
3794
4416
  }
3795
4417
  }
4418
+ function validateProjection(projection, diagnostics, field, viewpointId) {
4419
+ if (projection !== "topdown" && projection !== "isometric" && projection !== "orthographic" && projection !== "perspective") {
4420
+ diagnostics.push(error("validate.viewpoint.projection.invalid", `Unknown projection "${String(projection)}" in viewpoint "${viewpointId}".`, void 0, field));
4421
+ }
4422
+ }
4423
+ function validateCamera(camera, projection, rotationDeg, diagnostics, viewpointId, focusObjectId, selectedObjectId, filter, objectMap) {
4424
+ if (!camera) {
4425
+ return;
4426
+ }
4427
+ const prefix = `viewpoint.${viewpointId}.camera`;
4428
+ for (const [key, value] of [
4429
+ ["azimuth", camera.azimuth],
4430
+ ["elevation", camera.elevation],
4431
+ ["roll", camera.roll],
4432
+ ["distance", camera.distance]
4433
+ ]) {
4434
+ if (value !== null && (!Number.isFinite(value) || key === "distance" && value <= 0)) {
4435
+ diagnostics.push(error("validate.viewpoint.camera.invalid", `Invalid camera ${key} "${String(value)}" in viewpoint "${viewpointId}".`, void 0, `${prefix}.${key}`));
4436
+ }
4437
+ }
4438
+ if (camera.distance !== null && projection !== "perspective") {
4439
+ diagnostics.push(warn("validate.viewpoint.camera.distance.partialEffect", `Camera "distance" only has a semantic effect in perspective viewpoints; "${viewpointId}" uses "${projection}".`, void 0, `${prefix}.distance`));
4440
+ }
4441
+ if (projection === "topdown" && (camera.elevation !== null || camera.roll !== null)) {
4442
+ diagnostics.push(warn("validate.viewpoint.camera.topdownPartial", `Camera elevation/roll on topdown viewpoint "${viewpointId}" are currently stored for future 3D use and only partially affect 2D rendering.`, void 0, prefix));
4443
+ }
4444
+ if (projection === "isometric" && camera.elevation !== null) {
4445
+ diagnostics.push(info("validate.viewpoint.camera.isometricStored", `Camera elevation on isometric viewpoint "${viewpointId}" is preserved semantically for future 3D rendering.`, void 0, `${prefix}.elevation`));
4446
+ }
4447
+ if (camera.azimuth !== null && camera.azimuth !== 0 && rotationDeg !== 0) {
4448
+ diagnostics.push(warn("validate.viewpoint.rotation.cameraOverlap", `Viewpoint "${viewpointId}" uses camera.azimuth; keep "rotation" only for 2D screen rotation to avoid ambiguity.`, void 0, `${prefix}.azimuth`));
4449
+ }
4450
+ const hasAnchor = focusObjectId !== null && objectMap.has(focusObjectId) || selectedObjectId !== null && objectMap.has(selectedObjectId) || !!filter;
4451
+ if (!hasAnchor) {
4452
+ diagnostics.push(info("validate.viewpoint.camera.anchorMissing", `Viewpoint "${viewpointId}" stores camera settings without a focus object, selection, or filter anchor.`, void 0, prefix));
4453
+ }
4454
+ }
4455
+ function resolveEffectiveEpoch(system, object, event, pose) {
4456
+ return normalizeOptionalContextString(pose?.epoch) ?? normalizeOptionalContextString(event?.epoch) ?? normalizeOptionalContextString(object.epoch) ?? normalizeOptionalContextString(system?.epoch) ?? null;
4457
+ }
4458
+ function resolveEffectiveReferencePlane(system, object, event, pose) {
4459
+ return normalizeOptionalContextString(pose?.referencePlane) ?? normalizeOptionalContextString(event?.referencePlane) ?? normalizeOptionalContextString(object.referencePlane) ?? normalizeOptionalContextString(system?.referencePlane) ?? null;
4460
+ }
4461
+ function normalizeOptionalContextString(value) {
4462
+ return typeof value === "string" && value.trim() ? value.trim() : null;
4463
+ }
3796
4464
  function toleranceForField(object, field) {
3797
4465
  const tolerance = object.tolerances?.find((entry) => entry.field === field)?.value;
3798
4466
  if (typeof tolerance === "number") {
@@ -3888,6 +4556,23 @@ var WorldOrbit = (() => {
3888
4556
  });
3889
4557
  }
3890
4558
  var DRAFT_OBJECT_FIELD_KEYS = new Set(DRAFT_OBJECT_FIELD_SPECS.keys());
4559
+ var EVENT_POSE_FIELD_KEYS = /* @__PURE__ */ new Set([
4560
+ "orbit",
4561
+ "distance",
4562
+ "semiMajor",
4563
+ "eccentricity",
4564
+ "period",
4565
+ "angle",
4566
+ "inclination",
4567
+ "phase",
4568
+ "at",
4569
+ "surface",
4570
+ "free",
4571
+ "inner",
4572
+ "outer",
4573
+ "epoch",
4574
+ "referencePlane"
4575
+ ]);
3891
4576
  function parseWorldOrbitAtlas(source) {
3892
4577
  return parseAtlasSource(source);
3893
4578
  }
@@ -3905,12 +4590,15 @@ var WorldOrbit = (() => {
3905
4590
  const objectNodes = [];
3906
4591
  const groups = [];
3907
4592
  const relations = [];
4593
+ const events = [];
4594
+ const eventPoseNodes = /* @__PURE__ */ new Map();
3908
4595
  let sawDefaults = false;
3909
4596
  let sawAtlas = false;
3910
4597
  const viewpointIds = /* @__PURE__ */ new Set();
3911
4598
  const annotationIds = /* @__PURE__ */ new Set();
3912
4599
  const groupIds = /* @__PURE__ */ new Set();
3913
4600
  const relationIds = /* @__PURE__ */ new Set();
4601
+ const eventIds = /* @__PURE__ */ new Set();
3914
4602
  for (let index = 0; index < lines.length; index++) {
3915
4603
  const rawLine = lines[index];
3916
4604
  const lineNumber = index + 1;
@@ -3928,7 +4616,7 @@ var WorldOrbit = (() => {
3928
4616
  if (!sawSchemaHeader) {
3929
4617
  sourceSchemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
3930
4618
  sawSchemaHeader = true;
3931
- if (prepared.comments.length > 0 && sourceSchemaVersion !== "2.1") {
4619
+ if (prepared.comments.length > 0 && isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
3932
4620
  diagnostics.push({
3933
4621
  code: "parse.schema21.commentCompatibility",
3934
4622
  severity: "warning",
@@ -3941,7 +4629,7 @@ var WorldOrbit = (() => {
3941
4629
  continue;
3942
4630
  }
3943
4631
  if (indent === 0) {
3944
- section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, { sawDefaults, sawAtlas });
4632
+ section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, { sawDefaults, sawAtlas });
3945
4633
  if (section.kind === "system") {
3946
4634
  system = section.system;
3947
4635
  } else if (section.kind === "defaults") {
@@ -3960,6 +4648,7 @@ var WorldOrbit = (() => {
3960
4648
  throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
3961
4649
  }
3962
4650
  const objects = objectNodes.map((node) => normalizeDraftObject(node, sourceSchemaVersion, diagnostics));
4651
+ const normalizedEvents = events.map((event) => normalizeDraftEvent(event, eventPoseNodes.get(event.id) ?? []));
3963
4652
  const outputVersion = forcedOutputVersion ?? (sourceSchemaVersion === "2.0-draft" ? "2.0" : sourceSchemaVersion);
3964
4653
  const baseDocument = {
3965
4654
  format: "worldorbit",
@@ -3967,6 +4656,7 @@ var WorldOrbit = (() => {
3967
4656
  system,
3968
4657
  groups,
3969
4658
  relations,
4659
+ events: normalizedEvents,
3970
4660
  objects,
3971
4661
  diagnostics
3972
4662
  };
@@ -3996,13 +4686,13 @@ var WorldOrbit = (() => {
3996
4686
  return document2;
3997
4687
  }
3998
4688
  function assertDraftSchemaHeader(tokens, line) {
3999
- if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1"].includes(tokens[1].value.toLowerCase())) {
4000
- throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
4689
+ if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1", "2.5"].includes(tokens[1].value.toLowerCase())) {
4690
+ throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", "schema 2.5", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
4001
4691
  }
4002
4692
  const version = tokens[1].value.toLowerCase();
4003
- return version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
4693
+ return version === "2.5" ? "2.5" : version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
4004
4694
  }
4005
- function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, flags) {
4695
+ function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
4006
4696
  const keyword = tokens[0]?.value.toLowerCase();
4007
4697
  switch (keyword) {
4008
4698
  case "system":
@@ -4020,6 +4710,8 @@ var WorldOrbit = (() => {
4020
4710
  return {
4021
4711
  kind: "defaults",
4022
4712
  system,
4713
+ sourceSchemaVersion,
4714
+ diagnostics,
4023
4715
  seenFields: /* @__PURE__ */ new Set()
4024
4716
  };
4025
4717
  case "atlas":
@@ -4039,7 +4731,7 @@ var WorldOrbit = (() => {
4039
4731
  if (!system) {
4040
4732
  throw new WorldOrbitError('Atlas section "viewpoint" requires a preceding system declaration', line, tokens[0].column);
4041
4733
  }
4042
- return startViewpointSection(tokens, line, system, viewpointIds);
4734
+ return startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics);
4043
4735
  case "annotation":
4044
4736
  if (!system) {
4045
4737
  throw new WorldOrbitError('Atlas section "annotation" requires a preceding system declaration', line, tokens[0].column);
@@ -4051,6 +4743,9 @@ var WorldOrbit = (() => {
4051
4743
  case "relation":
4052
4744
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "relation", { line, column: tokens[0].column });
4053
4745
  return startRelationSection(tokens, line, relations, relationIds);
4746
+ case "event":
4747
+ warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "event", { line, column: tokens[0].column });
4748
+ return startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics);
4054
4749
  case "object":
4055
4750
  return startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes);
4056
4751
  default:
@@ -4087,7 +4782,7 @@ var WorldOrbit = (() => {
4087
4782
  seenFields: /* @__PURE__ */ new Set()
4088
4783
  };
4089
4784
  }
4090
- function startViewpointSection(tokens, line, system, viewpointIds) {
4785
+ function startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics) {
4091
4786
  if (tokens.length !== 2) {
4092
4787
  throw new WorldOrbitError("Invalid viewpoint declaration", line, tokens[0]?.column ?? 1);
4093
4788
  }
@@ -4104,10 +4799,12 @@ var WorldOrbit = (() => {
4104
4799
  summary: "",
4105
4800
  focusObjectId: null,
4106
4801
  selectedObjectId: null,
4802
+ events: [],
4107
4803
  projection: system.defaults.view,
4108
4804
  preset: system.defaults.preset,
4109
4805
  zoom: null,
4110
4806
  rotationDeg: 0,
4807
+ camera: null,
4111
4808
  layers: {},
4112
4809
  filter: null
4113
4810
  };
@@ -4116,10 +4813,15 @@ var WorldOrbit = (() => {
4116
4813
  return {
4117
4814
  kind: "viewpoint",
4118
4815
  viewpoint,
4816
+ sourceSchemaVersion,
4817
+ diagnostics,
4119
4818
  seenFields: /* @__PURE__ */ new Set(),
4120
4819
  inFilter: false,
4121
4820
  filterIndent: null,
4122
- seenFilterFields: /* @__PURE__ */ new Set()
4821
+ seenFilterFields: /* @__PURE__ */ new Set(),
4822
+ inCamera: false,
4823
+ cameraIndent: null,
4824
+ seenCameraFields: /* @__PURE__ */ new Set()
4123
4825
  };
4124
4826
  }
4125
4827
  function startAnnotationSection(tokens, line, system, annotationIds) {
@@ -4206,6 +4908,51 @@ var WorldOrbit = (() => {
4206
4908
  seenFields: /* @__PURE__ */ new Set()
4207
4909
  };
4208
4910
  }
4911
+ function startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics) {
4912
+ if (tokens.length !== 2) {
4913
+ throw new WorldOrbitError("Invalid event declaration", line, tokens[0]?.column ?? 1);
4914
+ }
4915
+ const id = normalizeIdentifier2(tokens[1].value);
4916
+ if (!id) {
4917
+ throw new WorldOrbitError("Event id must not be empty", line, tokens[1].column);
4918
+ }
4919
+ if (eventIds.has(id)) {
4920
+ throw new WorldOrbitError(`Duplicate event id "${id}"`, line, tokens[1].column);
4921
+ }
4922
+ const event = {
4923
+ id,
4924
+ kind: "",
4925
+ label: humanizeIdentifier3(id),
4926
+ summary: null,
4927
+ targetObjectId: null,
4928
+ participantObjectIds: [],
4929
+ timing: null,
4930
+ visibility: null,
4931
+ epoch: null,
4932
+ referencePlane: null,
4933
+ tags: [],
4934
+ color: null,
4935
+ hidden: false,
4936
+ positions: []
4937
+ };
4938
+ const rawPoses = [];
4939
+ events.push(event);
4940
+ eventPoseNodes.set(id, rawPoses);
4941
+ eventIds.add(id);
4942
+ return {
4943
+ kind: "event",
4944
+ event,
4945
+ sourceSchemaVersion,
4946
+ diagnostics,
4947
+ seenFields: /* @__PURE__ */ new Set(),
4948
+ rawPoses,
4949
+ inPositions: false,
4950
+ positionsIndent: null,
4951
+ activePose: null,
4952
+ poseIndent: null,
4953
+ activePoseSeenFields: /* @__PURE__ */ new Set()
4954
+ };
4955
+ }
4209
4956
  function startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes) {
4210
4957
  if (tokens.length < 3) {
4211
4958
  throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
@@ -4262,6 +5009,9 @@ var WorldOrbit = (() => {
4262
5009
  case "relation":
4263
5010
  applyRelationField(section, tokens, line);
4264
5011
  return;
5012
+ case "event":
5013
+ applyEventField(section, indent, tokens, line);
5014
+ return;
4265
5015
  case "object":
4266
5016
  applyObjectField(section, indent, tokens, line);
4267
5017
  return;
@@ -4304,6 +5054,12 @@ var WorldOrbit = (() => {
4304
5054
  const value = joinFieldValue(tokens, line);
4305
5055
  switch (key) {
4306
5056
  case "view":
5057
+ if (isSchema25Projection(value)) {
5058
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "defaults.view", {
5059
+ line,
5060
+ column: tokens[0].column
5061
+ });
5062
+ }
4307
5063
  section.system.defaults.view = parseProjectionValue(value, line, tokens[0].column);
4308
5064
  return;
4309
5065
  case "scale":
@@ -4343,14 +5099,36 @@ var WorldOrbit = (() => {
4343
5099
  throw new WorldOrbitError(`Unknown atlas field "${tokens[0].value}"`, line, tokens[0].column);
4344
5100
  }
4345
5101
  function applyViewpointField2(section, indent, tokens, line) {
5102
+ if (section.inCamera && indent <= (section.cameraIndent ?? 0)) {
5103
+ section.inCamera = false;
5104
+ section.cameraIndent = null;
5105
+ }
4346
5106
  if (section.inFilter && indent <= (section.filterIndent ?? 0)) {
4347
5107
  section.inFilter = false;
4348
5108
  section.filterIndent = null;
4349
5109
  }
5110
+ if (section.inCamera) {
5111
+ applyViewpointCameraField(section, tokens, line);
5112
+ return;
5113
+ }
4350
5114
  if (section.inFilter) {
4351
5115
  applyViewpointFilterField(section, tokens, line);
4352
5116
  return;
4353
5117
  }
5118
+ if (tokens.length === 1 && tokens[0].value.toLowerCase() === "camera") {
5119
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
5120
+ line,
5121
+ column: tokens[0].column
5122
+ });
5123
+ if (section.seenFields.has("camera")) {
5124
+ throw new WorldOrbitError('Duplicate viewpoint field "camera"', line, tokens[0].column);
5125
+ }
5126
+ section.seenFields.add("camera");
5127
+ section.inCamera = true;
5128
+ section.cameraIndent = indent;
5129
+ section.viewpoint.camera = section.viewpoint.camera ?? createEmptyViewCamera2();
5130
+ return;
5131
+ }
4354
5132
  if (tokens.length === 1 && tokens[0].value.toLowerCase() === "filter") {
4355
5133
  if (section.seenFields.has("filter")) {
4356
5134
  throw new WorldOrbitError('Duplicate viewpoint field "filter"', line, tokens[0].column);
@@ -4376,6 +5154,12 @@ var WorldOrbit = (() => {
4376
5154
  section.viewpoint.selectedObjectId = value;
4377
5155
  return;
4378
5156
  case "projection":
5157
+ if (isSchema25Projection(value)) {
5158
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "projection", {
5159
+ line,
5160
+ column: tokens[0].column
5161
+ });
5162
+ }
4379
5163
  section.viewpoint.projection = parseProjectionValue(value, line, tokens[0].column);
4380
5164
  return;
4381
5165
  case "preset":
@@ -4387,13 +5171,49 @@ var WorldOrbit = (() => {
4387
5171
  case "rotation":
4388
5172
  section.viewpoint.rotationDeg = parseFiniteNumber2(value, line, tokens[0].column, "rotation");
4389
5173
  return;
5174
+ case "camera":
5175
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
5176
+ line,
5177
+ column: tokens[0].column
5178
+ });
5179
+ section.viewpoint.camera = parseInlineViewCamera(tokens.slice(1), line, section.viewpoint.camera);
5180
+ return;
4390
5181
  case "layers":
4391
- section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line);
5182
+ section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line, section.sourceSchemaVersion, section.diagnostics);
5183
+ return;
5184
+ case "events":
5185
+ warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.events", {
5186
+ line,
5187
+ column: tokens[0].column
5188
+ });
5189
+ section.viewpoint.events = parseTokenList(tokens.slice(1), line, "events");
4392
5190
  return;
4393
5191
  default:
4394
5192
  throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
4395
5193
  }
4396
5194
  }
5195
+ function applyViewpointCameraField(section, tokens, line) {
5196
+ const key = requireUniqueField(tokens, section.seenCameraFields, line);
5197
+ const value = joinFieldValue(tokens, line);
5198
+ const camera = section.viewpoint.camera ?? createEmptyViewCamera2();
5199
+ switch (key) {
5200
+ case "azimuth":
5201
+ camera.azimuth = parseFiniteNumber2(value, line, tokens[0].column, "camera.azimuth");
5202
+ break;
5203
+ case "elevation":
5204
+ camera.elevation = parseFiniteNumber2(value, line, tokens[0].column, "camera.elevation");
5205
+ break;
5206
+ case "roll":
5207
+ camera.roll = parseFiniteNumber2(value, line, tokens[0].column, "camera.roll");
5208
+ break;
5209
+ case "distance":
5210
+ camera.distance = parsePositiveNumber2(value, line, tokens[0].column, "camera.distance");
5211
+ break;
5212
+ default:
5213
+ throw new WorldOrbitError(`Unknown viewpoint camera field "${tokens[0].value}"`, line, tokens[0].column);
5214
+ }
5215
+ section.viewpoint.camera = camera;
5216
+ }
4397
5217
  function applyViewpointFilterField(section, tokens, line) {
4398
5218
  const key = requireUniqueField(tokens, section.seenFilterFields, line);
4399
5219
  const filter = section.viewpoint.filter ?? createEmptyViewpointFilter2();
@@ -4493,6 +5313,126 @@ var WorldOrbit = (() => {
4493
5313
  throw new WorldOrbitError(`Unknown relation field "${tokens[0].value}"`, line, tokens[0].column);
4494
5314
  }
4495
5315
  }
5316
+ function applyEventField(section, indent, tokens, line) {
5317
+ if (section.activePose && indent <= (section.poseIndent ?? 0)) {
5318
+ section.activePose = null;
5319
+ section.poseIndent = null;
5320
+ section.activePoseSeenFields.clear();
5321
+ }
5322
+ if (!section.activePose && section.inPositions && indent <= (section.positionsIndent ?? 0)) {
5323
+ section.inPositions = false;
5324
+ section.positionsIndent = null;
5325
+ }
5326
+ if (section.activePose) {
5327
+ if (tokens[0]?.value === "epoch" || tokens[0]?.value === "referencePlane") {
5328
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, `pose.${tokens[0].value}`, {
5329
+ line,
5330
+ column: tokens[0]?.column ?? 1
5331
+ });
5332
+ }
5333
+ section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
5334
+ return;
5335
+ }
5336
+ if (section.inPositions) {
5337
+ if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "pose") {
5338
+ throw new WorldOrbitError(`Unknown event positions field "${tokens[0].value}"`, line, tokens[0]?.column ?? 1);
5339
+ }
5340
+ const objectId = tokens[1].value;
5341
+ if (!objectId.trim()) {
5342
+ throw new WorldOrbitError("Event pose object id must not be empty", line, tokens[1].column);
5343
+ }
5344
+ const rawPose = {
5345
+ objectId,
5346
+ fields: [],
5347
+ location: { line, column: tokens[0].column }
5348
+ };
5349
+ section.rawPoses.push(rawPose);
5350
+ section.activePose = rawPose;
5351
+ section.poseIndent = indent;
5352
+ section.activePoseSeenFields = /* @__PURE__ */ new Set();
5353
+ return;
5354
+ }
5355
+ if (tokens.length === 1 && tokens[0].value.toLowerCase() === "positions") {
5356
+ if (section.seenFields.has("positions")) {
5357
+ throw new WorldOrbitError('Duplicate event field "positions"', line, tokens[0].column);
5358
+ }
5359
+ section.seenFields.add("positions");
5360
+ section.inPositions = true;
5361
+ section.positionsIndent = indent;
5362
+ return;
5363
+ }
5364
+ const key = requireUniqueField(tokens, section.seenFields, line);
5365
+ switch (key) {
5366
+ case "kind":
5367
+ section.event.kind = joinFieldValue(tokens, line);
5368
+ return;
5369
+ case "label":
5370
+ section.event.label = joinFieldValue(tokens, line);
5371
+ return;
5372
+ case "summary":
5373
+ section.event.summary = joinFieldValue(tokens, line);
5374
+ return;
5375
+ case "target":
5376
+ section.event.targetObjectId = joinFieldValue(tokens, line);
5377
+ return;
5378
+ case "participants":
5379
+ section.event.participantObjectIds = parseTokenList(tokens.slice(1), line, "participants");
5380
+ return;
5381
+ case "timing":
5382
+ section.event.timing = joinFieldValue(tokens, line);
5383
+ return;
5384
+ case "visibility":
5385
+ section.event.visibility = joinFieldValue(tokens, line);
5386
+ return;
5387
+ case "epoch":
5388
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.epoch", {
5389
+ line,
5390
+ column: tokens[0].column
5391
+ });
5392
+ section.event.epoch = joinFieldValue(tokens, line);
5393
+ return;
5394
+ case "referenceplane":
5395
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.referencePlane", {
5396
+ line,
5397
+ column: tokens[0].column
5398
+ });
5399
+ section.event.referencePlane = joinFieldValue(tokens, line);
5400
+ return;
5401
+ case "tags":
5402
+ section.event.tags = parseTokenList(tokens.slice(1), line, "tags");
5403
+ return;
5404
+ case "color":
5405
+ section.event.color = joinFieldValue(tokens, line);
5406
+ return;
5407
+ case "hidden":
5408
+ section.event.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
5409
+ line,
5410
+ column: tokens[0].column
5411
+ });
5412
+ return;
5413
+ default:
5414
+ throw new WorldOrbitError(`Unknown event field "${tokens[0].value}"`, line, tokens[0].column);
5415
+ }
5416
+ }
5417
+ function parseEventPoseField(tokens, line, seenFields) {
5418
+ if (tokens.length < 2) {
5419
+ throw new WorldOrbitError("Invalid event pose field line", line, tokens[0]?.column ?? 1);
5420
+ }
5421
+ const key = tokens[0].value;
5422
+ if (!EVENT_POSE_FIELD_KEYS.has(key)) {
5423
+ throw new WorldOrbitError(`Unknown event pose field "${key}"`, line, tokens[0].column);
5424
+ }
5425
+ if (seenFields.has(key)) {
5426
+ throw new WorldOrbitError(`Duplicate event pose field "${key}"`, line, tokens[0].column);
5427
+ }
5428
+ seenFields.add(key);
5429
+ return {
5430
+ type: "field",
5431
+ key,
5432
+ values: tokens.slice(1).map((token) => token.value),
5433
+ location: { line, column: tokens[0].column }
5434
+ };
5435
+ }
4496
5436
  function applyObjectField(section, indent, tokens, line) {
4497
5437
  if (section.activeBlock && indent <= (section.blockIndent ?? 0)) {
4498
5438
  section.activeBlock = null;
@@ -4551,7 +5491,7 @@ var WorldOrbit = (() => {
4551
5491
  function parseObjectTypeTokens(tokens, line) {
4552
5492
  return parseTokenList(tokens, line, "objectTypes").filter((value) => value === "star" || value === "planet" || value === "moon" || value === "belt" || value === "asteroid" || value === "comet" || value === "ring" || value === "structure" || value === "phenomenon");
4553
5493
  }
4554
- function parseLayerTokens(tokens, line) {
5494
+ function parseLayerTokens(tokens, line, sourceSchemaVersion, diagnostics) {
4555
5495
  const layers = {};
4556
5496
  for (const token of parseTokenList(tokens, line, "layers")) {
4557
5497
  const enabled = !token.startsWith("-") && !token.startsWith("!");
@@ -4561,7 +5501,13 @@ var WorldOrbit = (() => {
4561
5501
  layers["orbits-front"] = enabled;
4562
5502
  continue;
4563
5503
  }
4564
- if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "objects" || raw === "labels" || raw === "metadata") {
5504
+ if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "events" || raw === "objects" || raw === "labels" || raw === "metadata") {
5505
+ if (raw === "events" && sourceSchemaVersion && diagnostics) {
5506
+ warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "layers.events", {
5507
+ line,
5508
+ column: tokens[0]?.column ?? 1
5509
+ });
5510
+ }
4565
5511
  layers[raw] = enabled;
4566
5512
  }
4567
5513
  }
@@ -4579,11 +5525,15 @@ var WorldOrbit = (() => {
4579
5525
  }
4580
5526
  function parseProjectionValue(value, line, column) {
4581
5527
  const normalized = value.toLowerCase();
4582
- if (normalized !== "topdown" && normalized !== "isometric") {
5528
+ if (normalized !== "topdown" && normalized !== "isometric" && normalized !== "orthographic" && normalized !== "perspective") {
4583
5529
  throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
4584
5530
  }
4585
5531
  return normalized;
4586
5532
  }
5533
+ function isSchema25Projection(value) {
5534
+ const normalized = value.toLowerCase();
5535
+ return normalized === "orthographic" || normalized === "perspective";
5536
+ }
4587
5537
  function parsePresetValue(value, line, column) {
4588
5538
  const normalized = value.toLowerCase();
4589
5539
  if (normalized === "diagram" || normalized === "presentation" || normalized === "atlas-card" || normalized === "markdown") {
@@ -4613,6 +5563,48 @@ var WorldOrbit = (() => {
4613
5563
  groupIds: []
4614
5564
  };
4615
5565
  }
5566
+ function createEmptyViewCamera2() {
5567
+ return {
5568
+ azimuth: null,
5569
+ elevation: null,
5570
+ roll: null,
5571
+ distance: null
5572
+ };
5573
+ }
5574
+ function parseInlineViewCamera(tokens, line, current) {
5575
+ if (tokens.length === 0 || tokens.length % 2 !== 0) {
5576
+ throw new WorldOrbitError('Field "camera" expects "<field> <value>" pairs', line, tokens[0]?.column ?? 1);
5577
+ }
5578
+ const camera = current ? { ...current } : createEmptyViewCamera2();
5579
+ const seen = /* @__PURE__ */ new Set();
5580
+ for (let index = 0; index < tokens.length; index += 2) {
5581
+ const fieldToken = tokens[index];
5582
+ const valueToken = tokens[index + 1];
5583
+ const key = fieldToken.value.toLowerCase();
5584
+ if (seen.has(key)) {
5585
+ throw new WorldOrbitError(`Duplicate viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
5586
+ }
5587
+ seen.add(key);
5588
+ const value = valueToken.value;
5589
+ switch (key) {
5590
+ case "azimuth":
5591
+ camera.azimuth = parseFiniteNumber2(value, line, fieldToken.column, "camera.azimuth");
5592
+ break;
5593
+ case "elevation":
5594
+ camera.elevation = parseFiniteNumber2(value, line, fieldToken.column, "camera.elevation");
5595
+ break;
5596
+ case "roll":
5597
+ camera.roll = parseFiniteNumber2(value, line, fieldToken.column, "camera.roll");
5598
+ break;
5599
+ case "distance":
5600
+ camera.distance = parsePositiveNumber2(value, line, fieldToken.column, "camera.distance");
5601
+ break;
5602
+ default:
5603
+ throw new WorldOrbitError(`Unknown viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
5604
+ }
5605
+ }
5606
+ return camera;
5607
+ }
4616
5608
  function parseInlineObjectFields(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
4617
5609
  const fields = [];
4618
5610
  let index = 0;
@@ -4700,7 +5692,7 @@ var WorldOrbit = (() => {
4700
5692
  }
4701
5693
  function normalizeDraftObject(node, sourceSchemaVersion, diagnostics) {
4702
5694
  const fieldMap = collectDraftFields(node.fields);
4703
- const placement = extractDraftPlacement(node.objectType, fieldMap);
5695
+ const placement = extractPlacementFromFieldMap(fieldMap);
4704
5696
  const properties = normalizeDraftProperties(node.objectType, fieldMap);
4705
5697
  const groups = parseOptionalTokenList(fieldMap.get("groups")?.[0]);
4706
5698
  const epoch = parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]);
@@ -4745,21 +5737,41 @@ var WorldOrbit = (() => {
4745
5737
  object.tolerances = tolerances;
4746
5738
  if (typedBlocks && Object.keys(typedBlocks).length > 0)
4747
5739
  object.typedBlocks = typedBlocks;
4748
- if (sourceSchemaVersion !== "2.1") {
5740
+ if (isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
4749
5741
  if (object.groups || object.epoch || object.referencePlane || object.tidalLock !== void 0 || object.resonance || object.renderHints || object.deriveRules?.length || object.validationRules?.length || object.lockedFields?.length || object.tolerances?.length || object.typedBlocks) {
4750
5742
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
4751
5743
  }
4752
5744
  }
4753
5745
  return object;
4754
5746
  }
4755
- function collectDraftFields(fields) {
5747
+ function normalizeDraftEvent(event, rawPoses) {
5748
+ return {
5749
+ ...event,
5750
+ participantObjectIds: [...new Set(event.participantObjectIds)],
5751
+ tags: [...new Set(event.tags)],
5752
+ positions: rawPoses.map((pose) => normalizeDraftEventPose(pose))
5753
+ };
5754
+ }
5755
+ function normalizeDraftEventPose(rawPose) {
5756
+ const fieldMap = collectDraftFields(rawPose.fields, "event-pose");
5757
+ const placement = extractPlacementFromFieldMap(fieldMap);
5758
+ return {
5759
+ objectId: rawPose.objectId,
5760
+ placement,
5761
+ inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
5762
+ outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer"),
5763
+ epoch: parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]),
5764
+ referencePlane: parseOptionalJoinedValue(fieldMap.get("referencePlane")?.[0])
5765
+ };
5766
+ }
5767
+ function collectDraftFields(fields, _mode = "object") {
4756
5768
  const grouped = /* @__PURE__ */ new Map();
4757
5769
  for (const field of fields) {
4758
5770
  const spec = getDraftObjectFieldSpec(field.key);
4759
- if (!spec) {
5771
+ if (!spec && !EVENT_POSE_FIELD_KEYS.has(field.key)) {
4760
5772
  throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
4761
5773
  }
4762
- if (!spec.allowRepeat && grouped.has(field.key)) {
5774
+ if (!spec?.allowRepeat && grouped.has(field.key)) {
4763
5775
  throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
4764
5776
  }
4765
5777
  const existing = grouped.get(field.key) ?? [];
@@ -4768,7 +5780,7 @@ var WorldOrbit = (() => {
4768
5780
  }
4769
5781
  return grouped;
4770
5782
  }
4771
- function extractDraftPlacement(objectType, fieldMap) {
5783
+ function extractPlacementFromFieldMap(fieldMap) {
4772
5784
  const orbitField = fieldMap.get("orbit")?.[0];
4773
5785
  const atField = fieldMap.get("at")?.[0];
4774
5786
  const surfaceField = fieldMap.get("surface")?.[0];
@@ -4936,7 +5948,7 @@ var WorldOrbit = (() => {
4936
5948
  }
4937
5949
  }
4938
5950
  function warnIfSchema21Feature(sourceSchemaVersion, diagnostics, featureName, location) {
4939
- if (sourceSchemaVersion === "2.1") {
5951
+ if (!isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
4940
5952
  return;
4941
5953
  }
4942
5954
  diagnostics.push({
@@ -4948,6 +5960,34 @@ var WorldOrbit = (() => {
4948
5960
  column: location.column
4949
5961
  });
4950
5962
  }
5963
+ function warnIfSchema25Feature(sourceSchemaVersion, diagnostics, featureName, location) {
5964
+ if (!isSchemaOlderThan(sourceSchemaVersion, "2.5")) {
5965
+ return;
5966
+ }
5967
+ diagnostics.push({
5968
+ code: "parse.schema25.featureCompatibility",
5969
+ severity: "warning",
5970
+ source: "parse",
5971
+ message: `Feature "${featureName}" requires schema 2.5; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
5972
+ line: location.line,
5973
+ column: location.column
5974
+ });
5975
+ }
5976
+ function isSchemaOlderThan(sourceSchemaVersion, requiredVersion) {
5977
+ return schemaVersionRank(sourceSchemaVersion) < schemaVersionRank(requiredVersion);
5978
+ }
5979
+ function schemaVersionRank(version) {
5980
+ switch (version) {
5981
+ case "2.0-draft":
5982
+ return 0;
5983
+ case "2.0":
5984
+ return 1;
5985
+ case "2.1":
5986
+ return 2;
5987
+ case "2.5":
5988
+ return 3;
5989
+ }
5990
+ }
4951
5991
  function preprocessAtlasSource(source) {
4952
5992
  const chars = [...source];
4953
5993
  const comments = [];
@@ -5035,7 +6075,7 @@ var WorldOrbit = (() => {
5035
6075
  }
5036
6076
 
5037
6077
  // packages/core/dist/atlas-edit.js
5038
- function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.0") {
6078
+ function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.5") {
5039
6079
  return {
5040
6080
  format: "worldorbit",
5041
6081
  version,
@@ -5061,6 +6101,7 @@ var WorldOrbit = (() => {
5061
6101
  },
5062
6102
  groups: [],
5063
6103
  relations: [],
6104
+ events: [],
5064
6105
  objects: [],
5065
6106
  diagnostics: []
5066
6107
  };
@@ -5087,6 +6128,12 @@ var WorldOrbit = (() => {
5087
6128
  for (const relation of [...document2.relations].sort(compareIdLike2)) {
5088
6129
  paths.push({ kind: "relation", id: relation.id });
5089
6130
  }
6131
+ for (const event of [...document2.events].sort(compareIdLike2)) {
6132
+ paths.push({ kind: "event", id: event.id });
6133
+ for (const pose of [...event.positions].sort(comparePoseObjectId2)) {
6134
+ paths.push({ kind: "event-pose", id: event.id, key: pose.objectId });
6135
+ }
6136
+ }
5090
6137
  for (const object of [...document2.objects].sort(compareIdLike2)) {
5091
6138
  paths.push({ kind: "object", id: object.id });
5092
6139
  }
@@ -5102,6 +6149,10 @@ var WorldOrbit = (() => {
5102
6149
  return path.key ? document2.system?.atlasMetadata[path.key] ?? null : null;
5103
6150
  case "group":
5104
6151
  return path.id ? findGroup(document2, path.id) : null;
6152
+ case "event":
6153
+ return path.id ? findEvent(document2, path.id) : null;
6154
+ case "event-pose":
6155
+ return path.id && path.key ? findEventPose(document2, path.id, path.key) : null;
5105
6156
  case "object":
5106
6157
  return path.id ? findObject(document2, path.id) : null;
5107
6158
  case "viewpoint":
@@ -5141,6 +6192,18 @@ var WorldOrbit = (() => {
5141
6192
  }
5142
6193
  upsertById(next.groups, value);
5143
6194
  return next;
6195
+ case "event":
6196
+ if (!path.id) {
6197
+ throw new Error('Event updates require an "id" value.');
6198
+ }
6199
+ upsertById(next.events, value);
6200
+ return next;
6201
+ case "event-pose":
6202
+ if (!path.id || !path.key) {
6203
+ throw new Error('Event pose updates require an event "id" and pose "key" value.');
6204
+ }
6205
+ upsertEventPose(next.events, path.id, value);
6206
+ return next;
5144
6207
  case "object":
5145
6208
  if (!path.id) {
5146
6209
  throw new Error('Object updates require an "id" value.');
@@ -5189,6 +6252,19 @@ var WorldOrbit = (() => {
5189
6252
  next.groups = next.groups.filter((group) => group.id !== path.id);
5190
6253
  }
5191
6254
  return next;
6255
+ case "event":
6256
+ if (path.id) {
6257
+ next.events = next.events.filter((event) => event.id !== path.id);
6258
+ }
6259
+ return next;
6260
+ case "event-pose":
6261
+ if (path.id && path.key) {
6262
+ const event = findEvent(next, path.id);
6263
+ if (event) {
6264
+ event.positions = event.positions.filter((pose) => pose.objectId !== path.key);
6265
+ }
6266
+ }
6267
+ return next;
5192
6268
  case "viewpoint":
5193
6269
  if (path.id) {
5194
6270
  system.viewpoints = system.viewpoints.filter((viewpoint) => viewpoint.id !== path.id);
@@ -5257,6 +6333,22 @@ var WorldOrbit = (() => {
5257
6333
  };
5258
6334
  }
5259
6335
  }
6336
+ if (diagnostic.field?.startsWith("event.")) {
6337
+ const parts = diagnostic.field.split(".");
6338
+ if (parts[1] && findEvent(document2, parts[1])) {
6339
+ if (parts[2] === "pose" && parts[3] && findEventPose(document2, parts[1], parts[3])) {
6340
+ return {
6341
+ kind: "event-pose",
6342
+ id: parts[1],
6343
+ key: parts[3]
6344
+ };
6345
+ }
6346
+ return {
6347
+ kind: "event",
6348
+ id: parts[1]
6349
+ };
6350
+ }
6351
+ }
5260
6352
  if (diagnostic.field && diagnostic.field in ensureSystem(document2).atlasMetadata) {
5261
6353
  return {
5262
6354
  kind: "metadata",
@@ -5288,6 +6380,12 @@ var WorldOrbit = (() => {
5288
6380
  function findRelation(document2, relationId) {
5289
6381
  return document2.relations.find((relation) => relation.id === relationId) ?? null;
5290
6382
  }
6383
+ function findEvent(document2, eventId) {
6384
+ return document2.events.find((event) => event.id === eventId) ?? null;
6385
+ }
6386
+ function findEventPose(document2, eventId, objectId) {
6387
+ return findEvent(document2, eventId)?.positions.find((pose) => pose.objectId === objectId) ?? null;
6388
+ }
5291
6389
  function findViewpoint(system, viewpointId) {
5292
6390
  return system?.viewpoints.find((viewpoint) => viewpoint.id === viewpointId) ?? null;
5293
6391
  }
@@ -5303,13 +6401,30 @@ var WorldOrbit = (() => {
5303
6401
  }
5304
6402
  items[index] = value;
5305
6403
  }
6404
+ function upsertEventPose(events, eventId, value) {
6405
+ const event = events.find((entry) => entry.id === eventId);
6406
+ if (!event) {
6407
+ throw new Error(`Unknown event "${eventId}" for pose update.`);
6408
+ }
6409
+ const index = event.positions.findIndex((entry) => entry.objectId === value.objectId);
6410
+ if (index === -1) {
6411
+ event.positions.push(value);
6412
+ event.positions.sort(comparePoseObjectId2);
6413
+ return;
6414
+ }
6415
+ event.positions[index] = value;
6416
+ }
5306
6417
  function compareIdLike2(left, right) {
5307
6418
  return left.id.localeCompare(right.id);
5308
6419
  }
6420
+ function comparePoseObjectId2(left, right) {
6421
+ return left.objectId.localeCompare(right.objectId);
6422
+ }
5309
6423
 
5310
6424
  // packages/core/dist/load.js
5311
- var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1)?$/i;
6425
+ var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1|\.5)?$/i;
5312
6426
  var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
6427
+ var ATLAS_SCHEMA_25_PATTERN = /^schema\s+2\.5$/i;
5313
6428
  var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
5314
6429
  function detectWorldOrbitSchemaVersion(source) {
5315
6430
  for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
@@ -5323,6 +6438,9 @@ var WorldOrbit = (() => {
5323
6438
  if (ATLAS_SCHEMA_21_PATTERN.test(trimmed)) {
5324
6439
  return "2.1";
5325
6440
  }
6441
+ if (ATLAS_SCHEMA_25_PATTERN.test(trimmed)) {
6442
+ return "2.5";
6443
+ }
5326
6444
  if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
5327
6445
  return "2.0";
5328
6446
  }
@@ -5383,7 +6501,7 @@ var WorldOrbit = (() => {
5383
6501
  }
5384
6502
  function loadWorldOrbitSourceWithDiagnostics(source) {
5385
6503
  const schemaVersion = detectWorldOrbitSchemaVersion(source);
5386
- if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1") {
6504
+ if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5") {
5387
6505
  return loadAtlasSourceWithDiagnostics(source, schemaVersion);
5388
6506
  }
5389
6507
  let ast;
@@ -5540,6 +6658,7 @@ var WorldOrbit = (() => {
5540
6658
  background: true,
5541
6659
  guides: true,
5542
6660
  relations: true,
6661
+ events: true,
5543
6662
  orbits: true,
5544
6663
  objects: true,
5545
6664
  labels: true,
@@ -5689,14 +6808,17 @@ var WorldOrbit = (() => {
5689
6808
  }
5690
6809
  function createAtlasStateSnapshot(viewerState, renderOptions, filter, viewpointId) {
5691
6810
  return {
5692
- version: "2.0",
6811
+ version: "2.5",
5693
6812
  viewpointId,
6813
+ activeEventId: renderOptions.activeEventId ?? null,
5694
6814
  viewerState: { ...viewerState },
5695
6815
  renderOptions: {
5696
6816
  preset: renderOptions.preset,
5697
6817
  projection: renderOptions.projection,
6818
+ camera: renderOptions.camera ? { ...renderOptions.camera } : null,
5698
6819
  layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
5699
- scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0
6820
+ scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
6821
+ activeEventId: renderOptions.activeEventId ?? null
5700
6822
  },
5701
6823
  filter: normalizeViewerFilter(filter)
5702
6824
  };
@@ -5707,8 +6829,9 @@ var WorldOrbit = (() => {
5707
6829
  function deserializeViewerAtlasState(serialized) {
5708
6830
  const raw = JSON.parse(decodeURIComponent(serialized));
5709
6831
  return {
5710
- version: "2.0",
6832
+ version: raw.version === "2.0" ? "2.0" : "2.5",
5711
6833
  viewpointId: raw.viewpointId ?? null,
6834
+ activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null,
5712
6835
  viewerState: {
5713
6836
  scale: raw.viewerState?.scale ?? 1,
5714
6837
  rotationDeg: raw.viewerState?.rotationDeg ?? 0,
@@ -5719,8 +6842,10 @@ var WorldOrbit = (() => {
5719
6842
  renderOptions: {
5720
6843
  preset: raw.renderOptions?.preset,
5721
6844
  projection: raw.renderOptions?.projection,
6845
+ camera: raw.renderOptions?.camera ? { ...raw.renderOptions.camera } : null,
5722
6846
  layers: raw.renderOptions?.layers ? { ...raw.renderOptions.layers } : void 0,
5723
- scaleModel: raw.renderOptions?.scaleModel ? { ...raw.renderOptions.scaleModel } : void 0
6847
+ scaleModel: raw.renderOptions?.scaleModel ? { ...raw.renderOptions.scaleModel } : void 0,
6848
+ activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null
5724
6849
  },
5725
6850
  filter: normalizeViewerFilter(raw.filter ?? null)
5726
6851
  };
@@ -5735,8 +6860,10 @@ var WorldOrbit = (() => {
5735
6860
  viewerState: { ...atlasState.viewerState },
5736
6861
  renderOptions: {
5737
6862
  ...atlasState.renderOptions,
6863
+ camera: atlasState.renderOptions.camera ? { ...atlasState.renderOptions.camera } : null,
5738
6864
  layers: atlasState.renderOptions.layers ? { ...atlasState.renderOptions.layers } : void 0,
5739
- scaleModel: atlasState.renderOptions.scaleModel ? { ...atlasState.renderOptions.scaleModel } : void 0
6865
+ scaleModel: atlasState.renderOptions.scaleModel ? { ...atlasState.renderOptions.scaleModel } : void 0,
6866
+ activeEventId: atlasState.renderOptions.activeEventId ?? null
5740
6867
  },
5741
6868
  filter: atlasState.filter ? { ...atlasState.filter } : null
5742
6869
  }
@@ -5754,6 +6881,7 @@ var WorldOrbit = (() => {
5754
6881
  background: viewpoint.layers.background,
5755
6882
  guides: viewpoint.layers.guides,
5756
6883
  relations: viewpoint.layers.relations,
6884
+ events: viewpoint.layers.events,
5757
6885
  orbits: viewpoint.layers["orbits-front"] === void 0 && viewpoint.layers["orbits-back"] === void 0 ? void 0 : viewpoint.layers["orbits-front"] !== false || viewpoint.layers["orbits-back"] !== false,
5758
6886
  objects: viewpoint.layers.objects,
5759
6887
  labels: viewpoint.layers.labels,
@@ -6040,6 +7168,7 @@ var WorldOrbit = (() => {
6040
7168
  const orbitMarkup = layers.orbits ? renderOrbitLayer(scene, visibleObjectIds, layers.structures) : { back: "", front: "" };
6041
7169
  const leaderMarkup = layers.guides ? scene.leaders.filter((leader) => !leader.hidden).filter((leader) => visibleObjectIds.has(leader.objectId)).filter((leader) => layers.structures || !isStructureLike(leader.object)).map((leader) => `<line class="wo-leader wo-leader-${leader.mode}" x1="${leader.x1}" y1="${leader.y1}" x2="${leader.x2}" y2="${leader.y2}" data-render-id="${escapeXml(leader.renderId)}" data-group-id="${escapeAttribute(leader.groupId ?? "")}" />`).join("") : "";
6042
7170
  const relationMarkup = layers.relations ? scene.relations.filter((relation) => !relation.hidden).filter((relation) => visibleObjectIds.has(relation.fromObjectId) && visibleObjectIds.has(relation.toObjectId)).map((relation) => `<line class="wo-relation" x1="${relation.x1}" y1="${relation.y1}" x2="${relation.x2}" y2="${relation.y2}" data-render-id="${escapeXml(relation.renderId)}" data-relation-id="${escapeAttribute(relation.relationId)}" />`).join("") : "";
7171
+ const eventMarkup = layers.events ? scene.events.filter((event) => !event.hidden).map((event) => renderSceneEventOverlay(scene, event, visibleObjectIds, theme)).join("") : "";
6043
7172
  const objectMarkup = layers.objects ? visibleObjects.map((object) => renderSceneObject(object, options.selectedObjectId ?? null, theme)).join("") : "";
6044
7173
  const labelMarkup = layers.labels ? visibleLabels.map((label) => renderSceneLabel(scene, label, options.selectedObjectId ?? null)).join("") : "";
6045
7174
  const metadataMarkup = layers.metadata ? `<text class="wo-title" x="56" y="64">${escapeXml(scene.title)}</text>
@@ -6075,6 +7204,9 @@ var WorldOrbit = (() => {
6075
7204
  .wo-orbit-front { opacity: 0.9; }
6076
7205
  .wo-orbit-band { stroke: ${theme.orbitBand}; stroke-linecap: round; }
6077
7206
  .wo-relation { stroke: ${theme.relation}; stroke-width: 2; stroke-dasharray: 10 6; }
7207
+ .wo-event-line { stroke: ${theme.accent}; stroke-width: 1.6; stroke-dasharray: 5 5; opacity: 0.72; }
7208
+ .wo-event-node { fill: ${theme.accent}; stroke: ${theme.selected}; stroke-width: 1.4; opacity: 0.92; }
7209
+ .wo-event-label { fill: ${theme.accent}; font-family: ${theme.fontFamily}; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
6078
7210
  .wo-leader { stroke: ${theme.leader}; stroke-width: 1.5; stroke-dasharray: 6 5; }
6079
7211
  .wo-label { fill: ${theme.ink}; font-family: ${theme.fontFamily}; font-weight: 600; letter-spacing: 0.02em; }
6080
7212
  .wo-label-secondary { fill: ${theme.muted}; font-family: ${theme.fontFamily}; font-weight: 500; }
@@ -6109,6 +7241,7 @@ var WorldOrbit = (() => {
6109
7241
  ${layers.orbits ? `<g data-layer-id="orbits-back">${orbitMarkup.back}</g>` : ""}
6110
7242
  ${layers.guides ? `<g data-layer-id="guides">${leaderMarkup}</g>` : ""}
6111
7243
  ${layers.relations ? `<g data-layer-id="relations">${relationMarkup}</g>` : ""}
7244
+ ${layers.events ? `<g data-layer-id="events">${eventMarkup}</g>` : ""}
6112
7245
  ${layers.objects ? `<g data-layer-id="objects">${objectMarkup}</g>` : ""}
6113
7246
  ${layers.orbits ? `<g data-layer-id="orbits-front">${orbitMarkup.front}</g>` : ""}
6114
7247
  ${layers.labels ? `<g data-layer-id="labels">${labelMarkup}</g>` : ""}
@@ -6116,6 +7249,20 @@ var WorldOrbit = (() => {
6116
7249
  </g>
6117
7250
  </g>
6118
7251
  </svg>`;
7252
+ }
7253
+ function renderSceneEventOverlay(scene, event, visibleObjectIds, theme) {
7254
+ const participants = event.objectIds.filter((objectId) => visibleObjectIds.has(objectId)).map((objectId) => scene.objects.find((object) => object.objectId === objectId && !object.hidden)).filter(Boolean);
7255
+ if (participants.length === 0) {
7256
+ return "";
7257
+ }
7258
+ const stroke = event.event.color || theme.accent;
7259
+ const label = event.event.label || event.event.id;
7260
+ const lineMarkup = participants.map((object) => `<line class="wo-event-line" x1="${event.x}" y1="${event.y}" x2="${object.x}" y2="${object.y}" stroke="${escapeAttribute(stroke)}" data-event-id="${escapeAttribute(event.eventId)}" data-object-id="${escapeAttribute(object.objectId)}" />`).join("");
7261
+ return `<g class="wo-event" data-render-id="${escapeXml(event.renderId)}" data-event-id="${escapeAttribute(event.eventId)}">
7262
+ ${lineMarkup}
7263
+ <circle class="wo-event-node" cx="${event.x}" cy="${event.y}" r="5" fill="${escapeAttribute(stroke)}" />
7264
+ <text class="wo-event-label" x="${event.x}" y="${event.y - 10}" text-anchor="middle" font-size="10">${escapeXml(label)}</text>
7265
+ </g>`;
6119
7266
  }
6120
7267
  function renderDocumentToSvg(document2, options = {}) {
6121
7268
  return renderSceneToSvg(renderDocumentToScene(document2, options), options);
@@ -6715,6 +7862,13 @@ var WorldOrbit = (() => {
6715
7862
  value: `${details.object.resonance.targetObjectId} ${details.object.resonance.ratio}`
6716
7863
  });
6717
7864
  }
7865
+ if (details.relatedEvents.length > 0) {
7866
+ fields.set("events", {
7867
+ key: "events",
7868
+ label: "Events",
7869
+ value: details.relatedEvents.map((event) => event.event.label || event.event.id).join(", ")
7870
+ });
7871
+ }
6718
7872
  if (placement?.mode === "at") {
6719
7873
  fields.set("placement", {
6720
7874
  key: "placement",
@@ -6819,6 +7973,7 @@ var WorldOrbit = (() => {
6819
7973
  padding: options.padding,
6820
7974
  preset: options.preset,
6821
7975
  projection: options.projection,
7976
+ camera: options.camera ? { ...options.camera } : null,
6822
7977
  scaleModel: options.scaleModel ? { ...options.scaleModel } : void 0,
6823
7978
  theme: options.theme,
6824
7979
  layers: options.layers,
@@ -7107,6 +8262,11 @@ var WorldOrbit = (() => {
7107
8262
  if (currentInput.kind !== "scene" && viewpoint.projection !== scene.projection) {
7108
8263
  nextRenderOptions.projection = viewpoint.projection;
7109
8264
  }
8265
+ if (viewpoint.camera) {
8266
+ nextRenderOptions.camera = { ...viewpoint.camera };
8267
+ } else if (renderOptions.camera) {
8268
+ nextRenderOptions.camera = null;
8269
+ }
7110
8270
  if (viewpointLayers) {
7111
8271
  nextRenderOptions.layers = viewpointLayers;
7112
8272
  }
@@ -7129,6 +8289,12 @@ var WorldOrbit = (() => {
7129
8289
  emitAtlasStateChange();
7130
8290
  return true;
7131
8291
  },
8292
+ getActiveEventId() {
8293
+ return renderOptions.activeEventId ?? null;
8294
+ },
8295
+ setActiveEvent(id) {
8296
+ api.setRenderOptions({ activeEventId: id });
8297
+ },
7132
8298
  search(query, limit = 12) {
7133
8299
  return searchSceneObjects(scene, query, limit);
7134
8300
  },
@@ -7393,6 +8559,7 @@ var WorldOrbit = (() => {
7393
8559
  orbit: scene.orbitVisuals.find((orbit) => orbit.objectId === renderObject.objectId && !orbit.hidden) ?? null,
7394
8560
  relatedOrbits: scene.orbitVisuals.filter((orbit) => !orbit.hidden && (orbit.objectId === renderObject.objectId || renderObject.ancestorIds.includes(orbit.objectId) || renderObject.childIds.includes(orbit.objectId))),
7395
8561
  relations: scene.relations.filter((relation) => !relation.hidden && (relation.fromObjectId === renderObject.objectId || relation.toObjectId === renderObject.objectId)),
8562
+ relatedEvents: scene.events.filter((event) => !event.hidden && (event.targetObjectId === renderObject.objectId || event.objectIds.includes(renderObject.objectId))),
7396
8563
  parent: getObjectById(renderObject.parentId),
7397
8564
  children: renderObject.childIds.map((childId) => getObjectById(childId)).filter(Boolean),
7398
8565
  ancestors: renderObject.ancestorIds.map((ancestorId) => getObjectById(ancestorId)).filter(Boolean),
@@ -7706,16 +8873,19 @@ var WorldOrbit = (() => {
7706
8873
  function cloneRenderOptions(renderOptions) {
7707
8874
  return {
7708
8875
  ...renderOptions,
8876
+ camera: renderOptions.camera ? { ...renderOptions.camera } : null,
7709
8877
  filter: renderOptions.filter ? { ...renderOptions.filter } : void 0,
7710
8878
  scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
7711
8879
  layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
7712
- theme: renderOptions.theme && typeof renderOptions.theme === "object" ? { ...renderOptions.theme } : renderOptions.theme
8880
+ theme: renderOptions.theme && typeof renderOptions.theme === "object" ? { ...renderOptions.theme } : renderOptions.theme,
8881
+ activeEventId: renderOptions.activeEventId ?? null
7713
8882
  };
7714
8883
  }
7715
8884
  function mergeRenderOptions(current, next) {
7716
8885
  return {
7717
8886
  ...current,
7718
8887
  ...next,
8888
+ camera: next.camera !== void 0 ? next.camera ? { ...next.camera } : null : current.camera ? { ...current.camera } : null,
7719
8889
  filter: next.filter !== void 0 ? normalizeViewerFilter(next.filter) : current.filter ? { ...current.filter } : void 0,
7720
8890
  scaleModel: next.scaleModel ? {
7721
8891
  ...current.scaleModel ?? {},
@@ -7729,7 +8899,7 @@ var WorldOrbit = (() => {
7729
8899
  };
7730
8900
  }
7731
8901
  function hasSceneAffectingRenderOptions(options) {
7732
- 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;
8902
+ return options.width !== void 0 || options.height !== void 0 || options.padding !== void 0 || options.preset !== void 0 || options.projection !== void 0 || options.camera !== void 0 || options.scaleModel !== void 0 || options.activeEventId !== void 0;
7733
8903
  }
7734
8904
  function resolveSourceRenderOptions2(loaded, renderOptions) {
7735
8905
  const atlasDocument = loaded.atlasDocument ?? loaded.draftDocument;
@@ -8326,6 +9496,8 @@ var WorldOrbit = (() => {
8326
9496
  }
8327
9497
  function buildInspectorSnapshot() {
8328
9498
  const activeViewer = requireViewer();
9499
+ const scene = activeViewer.getScene();
9500
+ const camera = scene.camera;
8329
9501
  return {
8330
9502
  selection: activeViewer.getSelectionDetails(),
8331
9503
  activeViewpoint: activeViewer.getActiveViewpoint(),
@@ -8333,13 +9505,21 @@ var WorldOrbit = (() => {
8333
9505
  atlasState: activeViewer.getAtlasState(),
8334
9506
  visibleObjectIds: activeViewer.getVisibleObjects().map((object) => object.objectId),
8335
9507
  scene: {
8336
- title: activeViewer.getScene().title,
8337
- projection: activeViewer.getScene().projection,
8338
- renderPreset: activeViewer.getScene().renderPreset,
8339
- groupCount: activeViewer.getScene().groups.length,
8340
- semanticGroupCount: activeViewer.getScene().semanticGroups.length,
8341
- relationCount: activeViewer.getScene().relations.length,
8342
- viewpointCount: activeViewer.getScene().viewpoints.length
9508
+ title: scene.title,
9509
+ projection: scene.projection,
9510
+ renderProjection: scene.renderProjection,
9511
+ camera: camera ? {
9512
+ azimuth: camera.azimuth,
9513
+ elevation: camera.elevation,
9514
+ roll: camera.roll,
9515
+ distance: camera.distance
9516
+ } : null,
9517
+ renderPreset: scene.renderPreset,
9518
+ groupCount: scene.groups.length,
9519
+ semanticGroupCount: scene.semanticGroups.length,
9520
+ relationCount: scene.relations.length,
9521
+ eventCount: scene.events.length,
9522
+ viewpointCount: scene.viewpoints.length
8343
9523
  }
8344
9524
  };
8345
9525
  }