worldorbit 2.5.15 → 2.5.17

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 (48) hide show
  1. package/README.md +1 -1
  2. package/dist/browser/core/dist/index.js +2444 -342
  3. package/dist/browser/editor/dist/index.js +11702 -0
  4. package/dist/browser/markdown/dist/index.js +2207 -392
  5. package/dist/browser/viewer/dist/index.js +2302 -382
  6. package/dist/unpkg/core/dist/index.js +2447 -345
  7. package/dist/unpkg/editor/dist/index.js +11727 -0
  8. package/dist/unpkg/markdown/dist/index.js +2210 -395
  9. package/dist/unpkg/viewer/dist/index.js +2305 -385
  10. package/dist/unpkg/worldorbit-core.min.js +12 -12
  11. package/dist/unpkg/worldorbit-editor.min.js +894 -0
  12. package/dist/unpkg/worldorbit-markdown.min.js +66 -58
  13. package/dist/unpkg/worldorbit-viewer.min.js +76 -68
  14. package/dist/unpkg/worldorbit.js +797 -78
  15. package/dist/unpkg/worldorbit.min.js +80 -72
  16. package/package.json +3 -2
  17. package/packages/core/dist/atlas-edit.js +74 -0
  18. package/packages/core/dist/atlas-validate.js +122 -8
  19. package/packages/core/dist/draft-parse.js +212 -8
  20. package/packages/core/dist/draft.d.ts +5 -2
  21. package/packages/core/dist/draft.js +59 -3
  22. package/packages/core/dist/format.js +63 -1
  23. package/packages/core/dist/normalize.js +1 -0
  24. package/packages/core/dist/scene.js +248 -46
  25. package/packages/core/dist/types.d.ts +41 -2
  26. package/packages/editor/dist/editor.d.ts +2 -0
  27. package/packages/editor/dist/editor.js +3578 -0
  28. package/packages/editor/dist/index.d.ts +2 -0
  29. package/packages/editor/dist/index.js +1 -0
  30. package/packages/editor/dist/types.d.ts +55 -0
  31. package/packages/editor/dist/types.js +1 -0
  32. package/packages/markdown/dist/html.d.ts +3 -0
  33. package/packages/markdown/dist/html.js +57 -0
  34. package/packages/markdown/dist/index.d.ts +4 -0
  35. package/packages/markdown/dist/index.js +3 -0
  36. package/packages/markdown/dist/rehype.d.ts +10 -0
  37. package/packages/markdown/dist/rehype.js +49 -0
  38. package/packages/markdown/dist/remark.d.ts +9 -0
  39. package/packages/markdown/dist/remark.js +28 -0
  40. package/packages/markdown/dist/types.d.ts +11 -0
  41. package/packages/markdown/dist/types.js +1 -0
  42. package/packages/viewer/dist/atlas-state.js +6 -0
  43. package/packages/viewer/dist/atlas-viewer.js +1 -0
  44. package/packages/viewer/dist/render.js +31 -2
  45. package/packages/viewer/dist/theme.js +1 -0
  46. package/packages/viewer/dist/tooltip.js +9 -0
  47. package/packages/viewer/dist/types.d.ts +8 -1
  48. package/packages/viewer/dist/viewer.js +12 -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
  }
@@ -1104,8 +1105,10 @@ var WorldOrbit = (() => {
1104
1105
  const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
1105
1106
  const spacingFactor = layoutPresetSpacing(layoutPreset);
1106
1107
  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);
1108
+ const activeEventId = options.activeEventId ?? null;
1109
+ const effectiveObjects = createEffectiveObjects(document2.objects, document2.events ?? [], activeEventId);
1110
+ const objectMap = new Map(effectiveObjects.map((object) => [object.id, object]));
1111
+ const relationships = buildSceneRelationships(effectiveObjects, objectMap);
1109
1112
  const positions = /* @__PURE__ */ new Map();
1110
1113
  const orbitDrafts = [];
1111
1114
  const leaderDrafts = [];
@@ -1114,7 +1117,7 @@ var WorldOrbit = (() => {
1114
1117
  const atObjects = [];
1115
1118
  const surfaceChildren = /* @__PURE__ */ new Map();
1116
1119
  const orbitChildren = /* @__PURE__ */ new Map();
1117
- for (const object of document2.objects) {
1120
+ for (const object of effectiveObjects) {
1118
1121
  const placement = object.placement;
1119
1122
  if (!placement) {
1120
1123
  rootObjects.push(object);
@@ -1209,13 +1212,14 @@ var WorldOrbit = (() => {
1209
1212
  const objects = [...positions.values()].map((position) => createSceneObject(position, scaleModel, relationships));
1210
1213
  const orbitVisuals = orbitDrafts.map((draft) => createOrbitVisual(draft, relationships.groupIds.get(draft.object.id) ?? null));
1211
1214
  const leaders = leaderDrafts.map((draft) => createLeaderLine(draft));
1212
- const labels = createSceneLabels(objects, height, scaleModel.labelMultiplier);
1215
+ const labels = createSceneLabels(objects, width, height, scaleModel.labelMultiplier);
1213
1216
  const relations = createSceneRelations(document2, objects);
1214
- const layers = createSceneLayers(orbitVisuals, relations, leaders, objects, labels);
1215
- const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships);
1217
+ const events = createSceneEvents(document2.events ?? [], objects, activeEventId);
1218
+ const layers = createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels);
1219
+ const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, scaleModel.labelMultiplier);
1216
1220
  const semanticGroups = createSceneSemanticGroups(document2, objects);
1217
1221
  const viewpoints = createSceneViewpoints(document2, projection, frame.preset, relationships, objectMap);
1218
- const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels);
1222
+ const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, scaleModel.labelMultiplier);
1219
1223
  return {
1220
1224
  width,
1221
1225
  height,
@@ -1241,6 +1245,8 @@ var WorldOrbit = (() => {
1241
1245
  groups,
1242
1246
  semanticGroups,
1243
1247
  viewpoints,
1248
+ events,
1249
+ activeEventId,
1244
1250
  objects,
1245
1251
  orbitVisuals,
1246
1252
  relations,
@@ -1259,6 +1265,35 @@ var WorldOrbit = (() => {
1259
1265
  y: center.y + dx * sin + dy * cos
1260
1266
  };
1261
1267
  }
1268
+ function createEffectiveObjects(objects, events, activeEventId) {
1269
+ const cloned = objects.map((object) => structuredClone(object));
1270
+ if (!activeEventId) {
1271
+ return cloned;
1272
+ }
1273
+ const activeEvent = events.find((event) => event.id === activeEventId);
1274
+ if (!activeEvent) {
1275
+ return cloned;
1276
+ }
1277
+ const objectMap = new Map(cloned.map((object) => [object.id, object]));
1278
+ for (const pose of activeEvent.positions) {
1279
+ const object = objectMap.get(pose.objectId);
1280
+ if (!object) {
1281
+ continue;
1282
+ }
1283
+ object.placement = pose.placement ? structuredClone(pose.placement) : null;
1284
+ if (pose.inner) {
1285
+ object.properties.inner = { ...pose.inner };
1286
+ } else {
1287
+ delete object.properties.inner;
1288
+ }
1289
+ if (pose.outer) {
1290
+ object.properties.outer = { ...pose.outer };
1291
+ } else {
1292
+ delete object.properties.outer;
1293
+ }
1294
+ }
1295
+ return cloned;
1296
+ }
1262
1297
  function resolveLayoutPreset(document2) {
1263
1298
  const rawScale = String(document2.system?.properties.scale ?? "balanced").toLowerCase();
1264
1299
  switch (rawScale) {
@@ -1414,24 +1449,14 @@ var WorldOrbit = (() => {
1414
1449
  hidden: draft.object.properties.hidden === true
1415
1450
  };
1416
1451
  }
1417
- function createSceneLabels(objects, sceneHeight, labelMultiplier) {
1452
+ function createSceneLabels(objects, sceneWidth, sceneHeight, labelMultiplier) {
1418
1453
  const labels = [];
1419
1454
  const occupied = [];
1420
- const visibleObjects = [...objects].filter((object) => !object.hidden && object.object.renderHints?.renderLabel !== false).sort((left, right) => left.sortKey - right.sortKey);
1455
+ const objectMap = new Map(objects.map((object) => [object.objectId, object]));
1456
+ const visibleObjects = [...objects].filter((object) => !object.hidden && object.object.renderHints?.renderLabel !== false).sort(compareLabelPlacementOrder);
1421
1457
  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);
1458
+ const placement = selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) ?? createLabelPlacement(object, defaultVerticalDirection(object, objectMap.get(object.parentId ?? "") ?? null, sceneHeight), 0, labelMultiplier);
1459
+ occupied.push(createLabelRect(object, placement, labelMultiplier));
1435
1460
  labels.push({
1436
1461
  renderId: `${object.renderId}-label`,
1437
1462
  objectId: object.objectId,
@@ -1440,17 +1465,128 @@ var WorldOrbit = (() => {
1440
1465
  semanticGroupIds: [...object.semanticGroupIds],
1441
1466
  label: object.label,
1442
1467
  secondaryLabel: object.secondaryLabel,
1443
- x: object.x,
1444
- y: labelY,
1445
- secondaryY,
1446
- textAnchor: "middle",
1447
- direction: direction < 0 ? "above" : "below",
1468
+ x: placement.x,
1469
+ y: placement.labelY,
1470
+ secondaryY: placement.secondaryY,
1471
+ textAnchor: placement.textAnchor,
1472
+ direction: placement.direction,
1448
1473
  hidden: object.hidden
1449
1474
  });
1450
1475
  }
1451
1476
  return labels;
1452
1477
  }
1453
- function createSceneLayers(orbitVisuals, relations, leaders, objects, labels) {
1478
+ function compareLabelPlacementOrder(left, right) {
1479
+ const priorityDiff = labelPlacementPriority(left) - labelPlacementPriority(right);
1480
+ if (priorityDiff !== 0) {
1481
+ return priorityDiff;
1482
+ }
1483
+ const renderPriorityDiff = (right.object.renderHints?.renderPriority ?? 0) - (left.object.renderHints?.renderPriority ?? 0);
1484
+ if (renderPriorityDiff !== 0) {
1485
+ return renderPriorityDiff;
1486
+ }
1487
+ return left.sortKey - right.sortKey;
1488
+ }
1489
+ function labelPlacementPriority(object) {
1490
+ switch (object.object.type) {
1491
+ case "star":
1492
+ return 0;
1493
+ case "planet":
1494
+ return 1;
1495
+ case "moon":
1496
+ return 2;
1497
+ case "belt":
1498
+ case "ring":
1499
+ return 3;
1500
+ case "asteroid":
1501
+ case "comet":
1502
+ return 4;
1503
+ case "structure":
1504
+ case "phenomenon":
1505
+ return 5;
1506
+ }
1507
+ }
1508
+ function selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) {
1509
+ for (const direction of preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight)) {
1510
+ const maxAttempts = direction === "left" || direction === "right" ? 4 : 6;
1511
+ for (let attempt = 0; attempt <= maxAttempts; attempt += 1) {
1512
+ const placement = createLabelPlacement(object, direction, attempt, labelMultiplier);
1513
+ const rect = createLabelRect(object, placement, labelMultiplier);
1514
+ if (!occupied.some((entry) => rectsOverlap(entry, rect))) {
1515
+ return placement;
1516
+ }
1517
+ }
1518
+ }
1519
+ return null;
1520
+ }
1521
+ function preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight) {
1522
+ const parent = object.parentId ? objectMap.get(object.parentId) ?? null : null;
1523
+ const vertical = defaultVerticalDirection(object, parent, sceneHeight);
1524
+ const oppositeVertical = vertical === "below" ? "above" : "below";
1525
+ const horizontal = defaultHorizontalDirection(object, parent, sceneWidth);
1526
+ const oppositeHorizontal = horizontal === "right" ? "left" : "right";
1527
+ 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";
1528
+ return preferHorizontal ? [horizontal, vertical, oppositeHorizontal, oppositeVertical] : [vertical, horizontal, oppositeVertical, oppositeHorizontal];
1529
+ }
1530
+ function defaultVerticalDirection(object, parent, sceneHeight) {
1531
+ if (parent && Math.abs(object.y - parent.y) > 6) {
1532
+ return object.y >= parent.y ? "below" : "above";
1533
+ }
1534
+ return object.y > sceneHeight * 0.62 ? "above" : "below";
1535
+ }
1536
+ function defaultHorizontalDirection(object, parent, sceneWidth) {
1537
+ if (parent && Math.abs(object.x - parent.x) > 6) {
1538
+ return object.x >= parent.x ? "right" : "left";
1539
+ }
1540
+ return object.x >= sceneWidth / 2 ? "right" : "left";
1541
+ }
1542
+ function createLabelPlacement(object, direction, attempt, labelMultiplier) {
1543
+ const step = 14 * labelMultiplier;
1544
+ switch (direction) {
1545
+ case "above": {
1546
+ const labelY = object.y - (object.radius + 18 * labelMultiplier + attempt * step);
1547
+ return {
1548
+ x: object.x,
1549
+ labelY,
1550
+ secondaryY: labelY - 16 * labelMultiplier,
1551
+ textAnchor: "middle",
1552
+ direction
1553
+ };
1554
+ }
1555
+ case "below": {
1556
+ const labelY = object.y + object.radius + 18 * labelMultiplier + attempt * step;
1557
+ return {
1558
+ x: object.x,
1559
+ labelY,
1560
+ secondaryY: labelY + 16 * labelMultiplier,
1561
+ textAnchor: "middle",
1562
+ direction
1563
+ };
1564
+ }
1565
+ case "left": {
1566
+ const x = object.x - (object.visualRadius + 16 * labelMultiplier + attempt * step);
1567
+ const labelY = object.y - 4 * labelMultiplier;
1568
+ return {
1569
+ x,
1570
+ labelY,
1571
+ secondaryY: labelY + 16 * labelMultiplier,
1572
+ textAnchor: "end",
1573
+ direction
1574
+ };
1575
+ }
1576
+ case "right": {
1577
+ const x = object.x + object.visualRadius + 16 * labelMultiplier + attempt * step;
1578
+ const labelY = object.y - 4 * labelMultiplier;
1579
+ return {
1580
+ x,
1581
+ labelY,
1582
+ secondaryY: labelY + 16 * labelMultiplier,
1583
+ textAnchor: "start",
1584
+ direction
1585
+ };
1586
+ }
1587
+ }
1588
+ }
1589
+ function createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels) {
1454
1590
  const backOrbitIds = orbitVisuals.filter((visual) => !visual.hidden && Boolean(visual.backArcPath)).map((visual) => visual.renderId);
1455
1591
  const frontOrbitIds = orbitVisuals.filter((visual) => !visual.hidden).map((visual) => visual.renderId);
1456
1592
  return [
@@ -1465,6 +1601,10 @@ var WorldOrbit = (() => {
1465
1601
  id: "relations",
1466
1602
  renderIds: relations.filter((relation) => !relation.hidden).map((relation) => relation.renderId)
1467
1603
  },
1604
+ {
1605
+ id: "events",
1606
+ renderIds: events.filter((event) => !event.hidden).map((event) => event.renderId)
1607
+ },
1468
1608
  {
1469
1609
  id: "objects",
1470
1610
  renderIds: objects.filter((object) => !object.hidden).map((object) => object.renderId)
@@ -1476,7 +1616,7 @@ var WorldOrbit = (() => {
1476
1616
  { id: "metadata", renderIds: ["wo-title", "wo-subtitle", "wo-meta"] }
1477
1617
  ];
1478
1618
  }
1479
- function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships) {
1619
+ function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, labelMultiplier) {
1480
1620
  const groups = /* @__PURE__ */ new Map();
1481
1621
  const ensureGroup = (groupId) => {
1482
1622
  if (!groupId) {
@@ -1525,7 +1665,7 @@ var WorldOrbit = (() => {
1525
1665
  }
1526
1666
  }
1527
1667
  for (const group of groups.values()) {
1528
- group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels);
1668
+ group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier);
1529
1669
  }
1530
1670
  return [...groups.values()].sort((left, right) => left.label.localeCompare(right.label));
1531
1671
  }
@@ -1559,6 +1699,29 @@ var WorldOrbit = (() => {
1559
1699
  };
1560
1700
  }).sort((left, right) => left.relation.id.localeCompare(right.relation.id));
1561
1701
  }
1702
+ function createSceneEvents(events, objects, activeEventId) {
1703
+ const objectMap = new Map(objects.map((object) => [object.objectId, object]));
1704
+ return events.map((event) => {
1705
+ const objectIds = [.../* @__PURE__ */ new Set([
1706
+ ...event.targetObjectId ? [event.targetObjectId] : [],
1707
+ ...event.participantObjectIds
1708
+ ])];
1709
+ const positions = objectIds.map((objectId) => objectMap.get(objectId)).filter(Boolean);
1710
+ const centroidX = positions.length > 0 ? positions.reduce((sum, object) => sum + object.x, 0) / positions.length : 0;
1711
+ const centroidY = positions.length > 0 ? positions.reduce((sum, object) => sum + object.y, 0) / positions.length : 0;
1712
+ return {
1713
+ renderId: `${createRenderId(event.id)}-event`,
1714
+ eventId: event.id,
1715
+ event,
1716
+ objectIds,
1717
+ participantIds: [...event.participantObjectIds],
1718
+ targetObjectId: event.targetObjectId,
1719
+ x: centroidX,
1720
+ y: centroidY,
1721
+ hidden: event.hidden || positions.length === 0 || positions.every((object) => object.hidden) || activeEventId !== null && event.id !== activeEventId
1722
+ };
1723
+ }).sort((left, right) => left.event.id.localeCompare(right.event.id));
1724
+ }
1562
1725
  function createSceneViewpoints(document2, projection, preset, relationships, objectMap) {
1563
1726
  const generatedOverview = createGeneratedOverviewViewpoint(document2, projection, preset);
1564
1727
  const drafts = /* @__PURE__ */ new Map();
@@ -1612,6 +1775,7 @@ var WorldOrbit = (() => {
1612
1775
  summary: "Fit the whole system with the current atlas defaults.",
1613
1776
  objectId: null,
1614
1777
  selectedObjectId: null,
1778
+ eventIds: [],
1615
1779
  projection,
1616
1780
  preset,
1617
1781
  rotationDeg: 0,
@@ -1648,6 +1812,9 @@ var WorldOrbit = (() => {
1648
1812
  draft.select = normalizedValue;
1649
1813
  }
1650
1814
  return;
1815
+ case "events":
1816
+ draft.eventIds = splitListValue(normalizedValue);
1817
+ return;
1651
1818
  case "projection":
1652
1819
  case "view":
1653
1820
  draft.projection = parseViewProjection(normalizedValue) ?? projection;
@@ -1704,6 +1871,7 @@ var WorldOrbit = (() => {
1704
1871
  summary: draft.summary?.trim() || createViewpointSummary(label, objectId, filter),
1705
1872
  objectId,
1706
1873
  selectedObjectId,
1874
+ eventIds: [...new Set(draft.eventIds ?? [])],
1707
1875
  projection: draft.projection ?? projection,
1708
1876
  preset: draft.preset ?? preset,
1709
1877
  rotationDeg: draft.rotationDeg ?? 0,
@@ -1761,7 +1929,7 @@ var WorldOrbit = (() => {
1761
1929
  next["orbits-front"] = enabled;
1762
1930
  continue;
1763
1931
  }
1764
- if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
1932
+ if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "events" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
1765
1933
  next[rawLayer] = enabled;
1766
1934
  }
1767
1935
  }
@@ -1809,7 +1977,7 @@ var WorldOrbit = (() => {
1809
1977
  }
1810
1978
  return parts.join(" - ");
1811
1979
  }
1812
- function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels) {
1980
+ function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, labelMultiplier) {
1813
1981
  let minX = Number.POSITIVE_INFINITY;
1814
1982
  let minY = Number.POSITIVE_INFINITY;
1815
1983
  let maxX = Number.NEGATIVE_INFINITY;
@@ -1839,7 +2007,7 @@ var WorldOrbit = (() => {
1839
2007
  for (const label of labels) {
1840
2008
  if (label.hidden)
1841
2009
  continue;
1842
- includeLabelBounds(label, include);
2010
+ includeLabelBounds(label, include, labelMultiplier);
1843
2011
  }
1844
2012
  if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
1845
2013
  return createBounds(0, 0, width, height);
@@ -1877,13 +2045,10 @@ var WorldOrbit = (() => {
1877
2045
  include(object.x - object.visualRadius - 24, object.y - object.visualRadius - 16);
1878
2046
  include(object.x + object.visualRadius + 24, object.y + object.visualRadius + 36);
1879
2047
  }
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);
2048
+ function includeLabelBounds(label, include, labelMultiplier) {
2049
+ const bounds = createLabelRectFromText(label.x, label.y, label.secondaryY, label.textAnchor, label.direction, label.label, label.secondaryLabel, labelMultiplier);
2050
+ include(bounds.left, bounds.top);
2051
+ include(bounds.right, bounds.bottom);
1887
2052
  }
1888
2053
  function placeObject(object, x, y, depth, positions, orbitDrafts, leaderDrafts, context) {
1889
2054
  if (positions.has(object.id)) {
@@ -2273,7 +2438,7 @@ var WorldOrbit = (() => {
2273
2438
  return null;
2274
2439
  }
2275
2440
  }
2276
- function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels) {
2441
+ function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier) {
2277
2442
  let minX = Number.POSITIVE_INFINITY;
2278
2443
  let minY = Number.POSITIVE_INFINITY;
2279
2444
  let maxX = Number.NEGATIVE_INFINITY;
@@ -2302,7 +2467,7 @@ var WorldOrbit = (() => {
2302
2467
  }
2303
2468
  for (const label of labels) {
2304
2469
  if (!label.hidden && group.labelIds.includes(label.objectId)) {
2305
- includeLabelBounds(label, include);
2470
+ includeLabelBounds(label, include, labelMultiplier);
2306
2471
  }
2307
2472
  }
2308
2473
  if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
@@ -2327,12 +2492,28 @@ var WorldOrbit = (() => {
2327
2492
  }
2328
2493
  return current.id;
2329
2494
  }
2330
- function createLabelRect(x, labelY, secondaryY, labelHalfWidth, direction) {
2495
+ function createLabelRect(object, placement, labelMultiplier) {
2496
+ return createLabelRectFromText(placement.x, placement.labelY, placement.secondaryY, placement.textAnchor, placement.direction, object.label, object.secondaryLabel, labelMultiplier);
2497
+ }
2498
+ function createLabelRectFromText(x, labelY, secondaryY, textAnchor, direction, label, secondaryLabel, labelMultiplier) {
2499
+ const labelHalfWidth = estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier);
2500
+ const labelWidth = labelHalfWidth * 2;
2501
+ const topPadding = direction === "above" ? 18 : 12;
2502
+ const bottomPadding = direction === "above" ? 8 : 12;
2503
+ let left = x - labelHalfWidth;
2504
+ let right = x + labelHalfWidth;
2505
+ if (textAnchor === "start") {
2506
+ left = x;
2507
+ right = x + labelWidth;
2508
+ } else if (textAnchor === "end") {
2509
+ left = x - labelWidth;
2510
+ right = x;
2511
+ }
2331
2512
  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)
2513
+ left,
2514
+ right,
2515
+ top: Math.min(labelY, secondaryY) - topPadding,
2516
+ bottom: Math.max(labelY, secondaryY) + bottomPadding
2336
2517
  };
2337
2518
  }
2338
2519
  function rectsOverlap(left, right) {
@@ -2519,11 +2700,6 @@ var WorldOrbit = (() => {
2519
2700
  function customColorFor(value) {
2520
2701
  return typeof value === "string" && value.trim() ? value : void 0;
2521
2702
  }
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
2703
  function estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier) {
2528
2704
  const primaryWidth = label.length * 4.6 * labelMultiplier + 18;
2529
2705
  const secondaryWidth = secondaryLabel.length * 3.9 * labelMultiplier + 18;
@@ -2563,6 +2739,7 @@ var WorldOrbit = (() => {
2563
2739
  system,
2564
2740
  groups: structuredClone(document2.groups ?? []),
2565
2741
  relations: structuredClone(document2.relations ?? []),
2742
+ events: structuredClone(document2.events ?? []),
2566
2743
  objects: document2.objects.map(cloneWorldOrbitObject),
2567
2744
  diagnostics
2568
2745
  };
@@ -2570,7 +2747,7 @@ var WorldOrbit = (() => {
2570
2747
  function upgradeDocumentToDraftV2(document2, options = {}) {
2571
2748
  return convertAtlasDocumentToLegacyDraft(upgradeDocumentToV2(document2, options));
2572
2749
  }
2573
- function materializeAtlasDocument(document2) {
2750
+ function materializeAtlasDocument(document2, options = {}) {
2574
2751
  const system = document2.system ? {
2575
2752
  type: "system",
2576
2753
  id: document2.system.id,
@@ -2581,6 +2758,8 @@ var WorldOrbit = (() => {
2581
2758
  properties: materializeDraftSystemProperties(document2.system),
2582
2759
  info: materializeDraftSystemInfo(document2.system)
2583
2760
  } : null;
2761
+ const objects = document2.objects.map(cloneWorldOrbitObject);
2762
+ applyEventPoseOverrides(objects, document2.events ?? [], options.activeEventId ?? null);
2584
2763
  return {
2585
2764
  format: "worldorbit",
2586
2765
  version: "1.0",
@@ -2588,7 +2767,8 @@ var WorldOrbit = (() => {
2588
2767
  system,
2589
2768
  groups: structuredClone(document2.groups ?? []),
2590
2769
  relations: structuredClone(document2.relations ?? []),
2591
- objects: document2.objects.map(cloneWorldOrbitObject)
2770
+ events: document2.events.map(cloneWorldOrbitEvent),
2771
+ objects
2592
2772
  };
2593
2773
  }
2594
2774
  function materializeDraftDocument(document2) {
@@ -2716,6 +2896,7 @@ var WorldOrbit = (() => {
2716
2896
  summary: viewpoint.summary,
2717
2897
  focusObjectId: viewpoint.objectId,
2718
2898
  selectedObjectId: viewpoint.selectedObjectId,
2899
+ events: [...viewpoint.eventIds],
2719
2900
  projection: viewpoint.projection,
2720
2901
  preset: viewpoint.preset,
2721
2902
  zoom: viewpoint.scale,
@@ -2748,6 +2929,52 @@ var WorldOrbit = (() => {
2748
2929
  info: { ...object.info }
2749
2930
  };
2750
2931
  }
2932
+ function cloneWorldOrbitEvent(event) {
2933
+ return {
2934
+ ...event,
2935
+ participantObjectIds: [...event.participantObjectIds],
2936
+ tags: [...event.tags],
2937
+ positions: event.positions.map(cloneWorldOrbitEventPose)
2938
+ };
2939
+ }
2940
+ function cloneWorldOrbitEventPose(pose) {
2941
+ return {
2942
+ objectId: pose.objectId,
2943
+ placement: clonePlacement(pose.placement),
2944
+ inner: pose.inner ? { ...pose.inner } : void 0,
2945
+ outer: pose.outer ? { ...pose.outer } : void 0
2946
+ };
2947
+ }
2948
+ function clonePlacement(placement) {
2949
+ return placement ? structuredClone(placement) : null;
2950
+ }
2951
+ function applyEventPoseOverrides(objects, events, activeEventId) {
2952
+ if (!activeEventId) {
2953
+ return;
2954
+ }
2955
+ const event = events.find((entry) => entry.id === activeEventId);
2956
+ if (!event) {
2957
+ return;
2958
+ }
2959
+ const objectMap = new Map(objects.map((object) => [object.id, object]));
2960
+ for (const pose of event.positions) {
2961
+ const object = objectMap.get(pose.objectId);
2962
+ if (!object) {
2963
+ continue;
2964
+ }
2965
+ object.placement = clonePlacement(pose.placement);
2966
+ if (pose.inner) {
2967
+ object.properties.inner = { ...pose.inner };
2968
+ } else {
2969
+ delete object.properties.inner;
2970
+ }
2971
+ if (pose.outer) {
2972
+ object.properties.outer = { ...pose.outer };
2973
+ } else {
2974
+ delete object.properties.outer;
2975
+ }
2976
+ }
2977
+ }
2751
2978
  function cloneProperties(properties) {
2752
2979
  const next = {};
2753
2980
  for (const [key, value] of Object.entries(properties)) {
@@ -2845,6 +3072,9 @@ var WorldOrbit = (() => {
2845
3072
  if ((viewpoint.filter?.groupIds.length ?? 0) > 0) {
2846
3073
  info2[`${prefix}.groups`] = viewpoint.filter?.groupIds.join(" ") ?? "";
2847
3074
  }
3075
+ if (viewpoint.events.length > 0) {
3076
+ info2[`${prefix}.events`] = viewpoint.events.join(" ");
3077
+ }
2848
3078
  }
2849
3079
  for (const annotation of system.annotations) {
2850
3080
  const prefix = `annotation.${annotation.id}`;
@@ -2869,7 +3099,7 @@ var WorldOrbit = (() => {
2869
3099
  if (orbitFront !== void 0 || orbitBack !== void 0) {
2870
3100
  tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
2871
3101
  }
2872
- for (const key of ["background", "guides", "relations", "objects", "labels", "metadata"]) {
3102
+ for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
2873
3103
  if (layers[key] !== void 0) {
2874
3104
  tokens.push(layers[key] ? key : `-${key}`);
2875
3105
  }
@@ -2973,6 +3203,10 @@ var WorldOrbit = (() => {
2973
3203
  lines.push("");
2974
3204
  lines.push(...formatAtlasRelation(relation));
2975
3205
  }
3206
+ for (const event of [...document2.events].sort(compareIdLike)) {
3207
+ lines.push("");
3208
+ lines.push(...formatAtlasEvent(event));
3209
+ }
2976
3210
  const sortedObjects = [...document2.objects].sort(compareObjects);
2977
3211
  if (sortedObjects.length > 0 && lines.at(-1) !== "") {
2978
3212
  lines.push("");
@@ -3003,6 +3237,10 @@ var WorldOrbit = (() => {
3003
3237
  lines.push("");
3004
3238
  lines.push(...formatAtlasRelation(relation));
3005
3239
  }
3240
+ for (const event of [...legacy.events].sort(compareIdLike)) {
3241
+ lines.push("");
3242
+ lines.push(...formatAtlasEvent(event));
3243
+ }
3006
3244
  const sortedObjects = [...legacy.objects].sort(compareObjects);
3007
3245
  if (sortedObjects.length > 0 && lines.at(-1) !== "") {
3008
3246
  lines.push("");
@@ -3214,6 +3452,9 @@ var WorldOrbit = (() => {
3214
3452
  if (layerTokens.length > 0) {
3215
3453
  lines.push(` layers ${layerTokens.join(" ")}`);
3216
3454
  }
3455
+ if (viewpoint.events.length > 0) {
3456
+ lines.push(` events ${viewpoint.events.join(" ")}`);
3457
+ }
3217
3458
  if (viewpoint.filter) {
3218
3459
  lines.push(" filter");
3219
3460
  if (viewpoint.filter.query) {
@@ -3286,6 +3527,54 @@ var WorldOrbit = (() => {
3286
3527
  }
3287
3528
  return lines;
3288
3529
  }
3530
+ function formatAtlasEvent(event) {
3531
+ const lines = [`event ${event.id}`, ` kind ${quoteIfNeeded(event.kind)}`];
3532
+ if (event.label) {
3533
+ lines.push(` label ${quoteIfNeeded(event.label)}`);
3534
+ }
3535
+ if (event.summary) {
3536
+ lines.push(` summary ${quoteIfNeeded(event.summary)}`);
3537
+ }
3538
+ if (event.targetObjectId) {
3539
+ lines.push(` target ${event.targetObjectId}`);
3540
+ }
3541
+ if (event.participantObjectIds.length > 0) {
3542
+ lines.push(` participants ${event.participantObjectIds.join(" ")}`);
3543
+ }
3544
+ if (event.timing) {
3545
+ lines.push(` timing ${quoteIfNeeded(event.timing)}`);
3546
+ }
3547
+ if (event.visibility) {
3548
+ lines.push(` visibility ${quoteIfNeeded(event.visibility)}`);
3549
+ }
3550
+ if (event.tags.length > 0) {
3551
+ lines.push(` tags ${event.tags.map(quoteIfNeeded).join(" ")}`);
3552
+ }
3553
+ if (event.color) {
3554
+ lines.push(` color ${quoteIfNeeded(event.color)}`);
3555
+ }
3556
+ if (event.hidden) {
3557
+ lines.push(" hidden true");
3558
+ }
3559
+ if (event.positions.length > 0) {
3560
+ lines.push("");
3561
+ lines.push(" positions");
3562
+ for (const pose of [...event.positions].sort(comparePoseObjectId)) {
3563
+ lines.push(` pose ${pose.objectId}`);
3564
+ for (const fieldLine of formatEventPoseFields(pose)) {
3565
+ lines.push(` ${fieldLine}`);
3566
+ }
3567
+ }
3568
+ }
3569
+ return lines;
3570
+ }
3571
+ function formatEventPoseFields(pose) {
3572
+ return [
3573
+ ...formatPlacement(pose.placement),
3574
+ ...formatOptionalUnit("inner", pose.inner),
3575
+ ...formatOptionalUnit("outer", pose.outer)
3576
+ ];
3577
+ }
3289
3578
  function formatValue(value) {
3290
3579
  if (Array.isArray(value)) {
3291
3580
  return value.map((item) => quoteIfNeeded(item)).join(" ");
@@ -3327,7 +3616,7 @@ var WorldOrbit = (() => {
3327
3616
  if (orbitFront !== void 0 || orbitBack !== void 0) {
3328
3617
  tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
3329
3618
  }
3330
- for (const key of ["background", "guides", "relations", "objects", "labels", "metadata"]) {
3619
+ for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
3331
3620
  if (layers[key] !== void 0) {
3332
3621
  tokens.push(layers[key] ? key : `-${key}`);
3333
3622
  }
@@ -3355,6 +3644,9 @@ var WorldOrbit = (() => {
3355
3644
  function compareIdLike(left, right) {
3356
3645
  return left.id.localeCompare(right.id);
3357
3646
  }
3647
+ function comparePoseObjectId(left, right) {
3648
+ return left.objectId.localeCompare(right.objectId);
3649
+ }
3358
3650
  function objectTypeIndex(objectType) {
3359
3651
  switch (objectType) {
3360
3652
  case "star":
@@ -3550,6 +3842,7 @@ var WorldOrbit = (() => {
3550
3842
  const diagnostics = [];
3551
3843
  const objectMap = new Map(document2.objects.map((object) => [object.id, object]));
3552
3844
  const groupIds = new Set(document2.groups.map((group) => group.id));
3845
+ const eventIds = new Set(document2.events.map((event) => event.id));
3553
3846
  if (!document2.system) {
3554
3847
  diagnostics.push(error("validate.system.required", "Atlas documents must declare exactly one system."));
3555
3848
  }
@@ -3559,6 +3852,7 @@ var WorldOrbit = (() => {
3559
3852
  ["viewpoint", document2.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []],
3560
3853
  ["annotation", document2.system?.annotations.map((annotation) => annotation.id) ?? []],
3561
3854
  ["relation", document2.relations.map((relation) => relation.id)],
3855
+ ["event", document2.events.map((event) => event.id)],
3562
3856
  ["object", document2.objects.map((object) => object.id)]
3563
3857
  ]) {
3564
3858
  for (const id of ids) {
@@ -3574,11 +3868,14 @@ var WorldOrbit = (() => {
3574
3868
  validateRelation(relation, objectMap, diagnostics);
3575
3869
  }
3576
3870
  for (const viewpoint of document2.system?.viewpoints ?? []) {
3577
- validateViewpointFilter(viewpoint.filter, groupIds, sourceSchemaVersion, diagnostics, viewpoint.id);
3871
+ validateViewpoint(viewpoint.filter, viewpoint.events ?? [], groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpoint.id);
3578
3872
  }
3579
3873
  for (const object of document2.objects) {
3580
3874
  validateObject(object, document2.system, objectMap, groupIds, diagnostics);
3581
3875
  }
3876
+ for (const event of document2.events) {
3877
+ validateEvent(event, objectMap, diagnostics);
3878
+ }
3582
3879
  return diagnostics;
3583
3880
  }
3584
3881
  function validateRelation(relation, objectMap, diagnostics) {
@@ -3596,13 +3893,19 @@ var WorldOrbit = (() => {
3596
3893
  diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
3597
3894
  }
3598
3895
  }
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}".`));
3896
+ function validateViewpoint(filter, eventRefs, groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpointId) {
3897
+ if (sourceSchemaVersion === "2.1") {
3898
+ if (filter) {
3899
+ for (const groupId of filter.groupIds) {
3900
+ if (!groupIds.has(groupId)) {
3901
+ diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.groups`));
3902
+ }
3903
+ }
3904
+ }
3905
+ for (const eventId of eventRefs) {
3906
+ if (!eventIds.has(eventId)) {
3907
+ diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.events`));
3908
+ }
3606
3909
  }
3607
3910
  }
3608
3911
  }
@@ -3688,6 +3991,103 @@ var WorldOrbit = (() => {
3688
3991
  }
3689
3992
  }
3690
3993
  }
3994
+ function validateEvent(event, objectMap, diagnostics) {
3995
+ const fieldPrefix = `event.${event.id}`;
3996
+ const referencedIds = /* @__PURE__ */ new Set();
3997
+ if (!event.kind.trim()) {
3998
+ diagnostics.push(error("validate.event.kind.required", `Event "${event.id}" is missing a "kind" value.`, void 0, `${fieldPrefix}.kind`));
3999
+ }
4000
+ if (!event.targetObjectId && event.participantObjectIds.length === 0) {
4001
+ diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
4002
+ }
4003
+ if (event.targetObjectId) {
4004
+ referencedIds.add(event.targetObjectId);
4005
+ if (!objectMap.has(event.targetObjectId)) {
4006
+ diagnostics.push(error("validate.event.target.unknown", `Unknown event target "${event.targetObjectId}" on "${event.id}".`, void 0, `${fieldPrefix}.target`));
4007
+ }
4008
+ }
4009
+ const seenParticipants = /* @__PURE__ */ new Set();
4010
+ for (const participantId of event.participantObjectIds) {
4011
+ referencedIds.add(participantId);
4012
+ if (seenParticipants.has(participantId)) {
4013
+ diagnostics.push(warn("validate.event.participants.duplicate", `Event "${event.id}" repeats participant "${participantId}".`, void 0, `${fieldPrefix}.participants`));
4014
+ continue;
4015
+ }
4016
+ seenParticipants.add(participantId);
4017
+ if (!objectMap.has(participantId)) {
4018
+ diagnostics.push(error("validate.event.participants.unknown", `Unknown event participant "${participantId}" on "${event.id}".`, void 0, `${fieldPrefix}.participants`));
4019
+ }
4020
+ }
4021
+ if (event.targetObjectId && event.participantObjectIds.length > 0 && !event.participantObjectIds.includes(event.targetObjectId)) {
4022
+ diagnostics.push(warn("validate.event.target.notParticipant", `Event "${event.id}" defines a target outside its participants list.`, void 0, `${fieldPrefix}.target`));
4023
+ }
4024
+ if (event.positions.length === 0) {
4025
+ diagnostics.push(warn("validate.event.positions.missing", `Event "${event.id}" has no positions block and cannot drive a scene snapshot.`, void 0, `${fieldPrefix}.positions`));
4026
+ }
4027
+ if (/(?:^|[-_])(solar-eclipse|lunar-eclipse|transit|occultation)(?:$|[-_])/.test(event.kind) && referencedIds.size < 3) {
4028
+ 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`));
4029
+ }
4030
+ const poseIds = /* @__PURE__ */ new Set();
4031
+ for (const pose of event.positions) {
4032
+ const poseFieldPrefix = `${fieldPrefix}.pose.${pose.objectId}`;
4033
+ if (poseIds.has(pose.objectId)) {
4034
+ diagnostics.push(error("validate.event.pose.duplicate", `Event "${event.id}" defines "${pose.objectId}" more than once in positions.`, void 0, poseFieldPrefix));
4035
+ continue;
4036
+ }
4037
+ poseIds.add(pose.objectId);
4038
+ const object = objectMap.get(pose.objectId);
4039
+ if (!object) {
4040
+ diagnostics.push(error("validate.event.pose.object.unknown", `Unknown event pose object "${pose.objectId}" on "${event.id}".`, void 0, poseFieldPrefix));
4041
+ continue;
4042
+ }
4043
+ if (!referencedIds.has(pose.objectId)) {
4044
+ diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
4045
+ }
4046
+ validateEventPose(pose, object, objectMap, diagnostics, poseFieldPrefix, event.id);
4047
+ }
4048
+ }
4049
+ function validateEventPose(pose, object, objectMap, diagnostics, fieldPrefix, eventId) {
4050
+ const placement = pose.placement;
4051
+ if (!placement) {
4052
+ diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
4053
+ return;
4054
+ }
4055
+ if (placement.mode === "orbit") {
4056
+ if (!objectMap.has(placement.target)) {
4057
+ diagnostics.push(error("validate.event.pose.orbit.target.unknown", `Unknown event orbit target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.orbit`));
4058
+ }
4059
+ if (placement.distance && placement.semiMajor) {
4060
+ diagnostics.push(error("validate.event.pose.orbit.distanceConflict", `Event "${eventId}" pose "${pose.objectId}" cannot declare both "distance" and "semiMajor".`, void 0, `${fieldPrefix}.distance`));
4061
+ }
4062
+ return;
4063
+ }
4064
+ if (placement.mode === "surface") {
4065
+ const target = objectMap.get(placement.target);
4066
+ if (!target) {
4067
+ diagnostics.push(error("validate.event.pose.surface.target.unknown", `Unknown event surface target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.surface`));
4068
+ } else if (!SURFACE_TARGET_TYPES2.has(target.type)) {
4069
+ 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`));
4070
+ }
4071
+ return;
4072
+ }
4073
+ if (placement.mode === "at") {
4074
+ if (object.type !== "structure" && object.type !== "phenomenon") {
4075
+ 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`));
4076
+ }
4077
+ const reference = placement.reference;
4078
+ if (reference.kind === "named" && !objectMap.has(reference.name)) {
4079
+ diagnostics.push(error("validate.event.pose.at.target.unknown", `Unknown event at-reference target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
4080
+ } else if (reference.kind === "anchor" && !objectMap.has(reference.objectId)) {
4081
+ diagnostics.push(error("validate.event.pose.anchor.target.unknown", `Unknown event anchor target "${reference.objectId}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
4082
+ } else if (reference.kind === "lagrange") {
4083
+ if (!objectMap.has(reference.primary)) {
4084
+ diagnostics.push(error("validate.event.pose.lagrange.primary.unknown", `Unknown event Lagrange target "${reference.primary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
4085
+ } else if (reference.secondary && !objectMap.has(reference.secondary)) {
4086
+ diagnostics.push(error("validate.event.pose.lagrange.secondary.unknown", `Unknown event Lagrange target "${reference.secondary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
4087
+ }
4088
+ }
4089
+ }
4090
+ }
3691
4091
  function validateAtTarget(object, objectMap, diagnostics) {
3692
4092
  const reference = object.placement?.mode === "at" ? object.placement.reference : null;
3693
4093
  if (!reference) {
@@ -3888,6 +4288,21 @@ var WorldOrbit = (() => {
3888
4288
  });
3889
4289
  }
3890
4290
  var DRAFT_OBJECT_FIELD_KEYS = new Set(DRAFT_OBJECT_FIELD_SPECS.keys());
4291
+ var EVENT_POSE_FIELD_KEYS = /* @__PURE__ */ new Set([
4292
+ "orbit",
4293
+ "distance",
4294
+ "semiMajor",
4295
+ "eccentricity",
4296
+ "period",
4297
+ "angle",
4298
+ "inclination",
4299
+ "phase",
4300
+ "at",
4301
+ "surface",
4302
+ "free",
4303
+ "inner",
4304
+ "outer"
4305
+ ]);
3891
4306
  function parseWorldOrbitAtlas(source) {
3892
4307
  return parseAtlasSource(source);
3893
4308
  }
@@ -3905,12 +4320,15 @@ var WorldOrbit = (() => {
3905
4320
  const objectNodes = [];
3906
4321
  const groups = [];
3907
4322
  const relations = [];
4323
+ const events = [];
4324
+ const eventPoseNodes = /* @__PURE__ */ new Map();
3908
4325
  let sawDefaults = false;
3909
4326
  let sawAtlas = false;
3910
4327
  const viewpointIds = /* @__PURE__ */ new Set();
3911
4328
  const annotationIds = /* @__PURE__ */ new Set();
3912
4329
  const groupIds = /* @__PURE__ */ new Set();
3913
4330
  const relationIds = /* @__PURE__ */ new Set();
4331
+ const eventIds = /* @__PURE__ */ new Set();
3914
4332
  for (let index = 0; index < lines.length; index++) {
3915
4333
  const rawLine = lines[index];
3916
4334
  const lineNumber = index + 1;
@@ -3941,7 +4359,7 @@ var WorldOrbit = (() => {
3941
4359
  continue;
3942
4360
  }
3943
4361
  if (indent === 0) {
3944
- section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, { sawDefaults, sawAtlas });
4362
+ section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, { sawDefaults, sawAtlas });
3945
4363
  if (section.kind === "system") {
3946
4364
  system = section.system;
3947
4365
  } else if (section.kind === "defaults") {
@@ -3960,6 +4378,7 @@ var WorldOrbit = (() => {
3960
4378
  throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
3961
4379
  }
3962
4380
  const objects = objectNodes.map((node) => normalizeDraftObject(node, sourceSchemaVersion, diagnostics));
4381
+ const normalizedEvents = events.map((event) => normalizeDraftEvent(event, eventPoseNodes.get(event.id) ?? []));
3963
4382
  const outputVersion = forcedOutputVersion ?? (sourceSchemaVersion === "2.0-draft" ? "2.0" : sourceSchemaVersion);
3964
4383
  const baseDocument = {
3965
4384
  format: "worldorbit",
@@ -3967,6 +4386,7 @@ var WorldOrbit = (() => {
3967
4386
  system,
3968
4387
  groups,
3969
4388
  relations,
4389
+ events: normalizedEvents,
3970
4390
  objects,
3971
4391
  diagnostics
3972
4392
  };
@@ -4002,7 +4422,7 @@ var WorldOrbit = (() => {
4002
4422
  const version = tokens[1].value.toLowerCase();
4003
4423
  return version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
4004
4424
  }
4005
- function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, flags) {
4425
+ function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
4006
4426
  const keyword = tokens[0]?.value.toLowerCase();
4007
4427
  switch (keyword) {
4008
4428
  case "system":
@@ -4039,7 +4459,7 @@ var WorldOrbit = (() => {
4039
4459
  if (!system) {
4040
4460
  throw new WorldOrbitError('Atlas section "viewpoint" requires a preceding system declaration', line, tokens[0].column);
4041
4461
  }
4042
- return startViewpointSection(tokens, line, system, viewpointIds);
4462
+ return startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics);
4043
4463
  case "annotation":
4044
4464
  if (!system) {
4045
4465
  throw new WorldOrbitError('Atlas section "annotation" requires a preceding system declaration', line, tokens[0].column);
@@ -4051,6 +4471,9 @@ var WorldOrbit = (() => {
4051
4471
  case "relation":
4052
4472
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "relation", { line, column: tokens[0].column });
4053
4473
  return startRelationSection(tokens, line, relations, relationIds);
4474
+ case "event":
4475
+ warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "event", { line, column: tokens[0].column });
4476
+ return startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics);
4054
4477
  case "object":
4055
4478
  return startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes);
4056
4479
  default:
@@ -4087,7 +4510,7 @@ var WorldOrbit = (() => {
4087
4510
  seenFields: /* @__PURE__ */ new Set()
4088
4511
  };
4089
4512
  }
4090
- function startViewpointSection(tokens, line, system, viewpointIds) {
4513
+ function startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics) {
4091
4514
  if (tokens.length !== 2) {
4092
4515
  throw new WorldOrbitError("Invalid viewpoint declaration", line, tokens[0]?.column ?? 1);
4093
4516
  }
@@ -4104,6 +4527,7 @@ var WorldOrbit = (() => {
4104
4527
  summary: "",
4105
4528
  focusObjectId: null,
4106
4529
  selectedObjectId: null,
4530
+ events: [],
4107
4531
  projection: system.defaults.view,
4108
4532
  preset: system.defaults.preset,
4109
4533
  zoom: null,
@@ -4116,6 +4540,8 @@ var WorldOrbit = (() => {
4116
4540
  return {
4117
4541
  kind: "viewpoint",
4118
4542
  viewpoint,
4543
+ sourceSchemaVersion,
4544
+ diagnostics,
4119
4545
  seenFields: /* @__PURE__ */ new Set(),
4120
4546
  inFilter: false,
4121
4547
  filterIndent: null,
@@ -4206,6 +4632,49 @@ var WorldOrbit = (() => {
4206
4632
  seenFields: /* @__PURE__ */ new Set()
4207
4633
  };
4208
4634
  }
4635
+ function startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics) {
4636
+ if (tokens.length !== 2) {
4637
+ throw new WorldOrbitError("Invalid event declaration", line, tokens[0]?.column ?? 1);
4638
+ }
4639
+ const id = normalizeIdentifier2(tokens[1].value);
4640
+ if (!id) {
4641
+ throw new WorldOrbitError("Event id must not be empty", line, tokens[1].column);
4642
+ }
4643
+ if (eventIds.has(id)) {
4644
+ throw new WorldOrbitError(`Duplicate event id "${id}"`, line, tokens[1].column);
4645
+ }
4646
+ const event = {
4647
+ id,
4648
+ kind: "",
4649
+ label: humanizeIdentifier3(id),
4650
+ summary: null,
4651
+ targetObjectId: null,
4652
+ participantObjectIds: [],
4653
+ timing: null,
4654
+ visibility: null,
4655
+ tags: [],
4656
+ color: null,
4657
+ hidden: false,
4658
+ positions: []
4659
+ };
4660
+ const rawPoses = [];
4661
+ events.push(event);
4662
+ eventPoseNodes.set(id, rawPoses);
4663
+ eventIds.add(id);
4664
+ return {
4665
+ kind: "event",
4666
+ event,
4667
+ sourceSchemaVersion,
4668
+ diagnostics,
4669
+ seenFields: /* @__PURE__ */ new Set(),
4670
+ rawPoses,
4671
+ inPositions: false,
4672
+ positionsIndent: null,
4673
+ activePose: null,
4674
+ poseIndent: null,
4675
+ activePoseSeenFields: /* @__PURE__ */ new Set()
4676
+ };
4677
+ }
4209
4678
  function startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes) {
4210
4679
  if (tokens.length < 3) {
4211
4680
  throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
@@ -4262,6 +4731,9 @@ var WorldOrbit = (() => {
4262
4731
  case "relation":
4263
4732
  applyRelationField(section, tokens, line);
4264
4733
  return;
4734
+ case "event":
4735
+ applyEventField(section, indent, tokens, line);
4736
+ return;
4265
4737
  case "object":
4266
4738
  applyObjectField(section, indent, tokens, line);
4267
4739
  return;
@@ -4388,7 +4860,14 @@ var WorldOrbit = (() => {
4388
4860
  section.viewpoint.rotationDeg = parseFiniteNumber2(value, line, tokens[0].column, "rotation");
4389
4861
  return;
4390
4862
  case "layers":
4391
- section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line);
4863
+ section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line, section.sourceSchemaVersion, section.diagnostics);
4864
+ return;
4865
+ case "events":
4866
+ warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.events", {
4867
+ line,
4868
+ column: tokens[0].column
4869
+ });
4870
+ section.viewpoint.events = parseTokenList(tokens.slice(1), line, "events");
4392
4871
  return;
4393
4872
  default:
4394
4873
  throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
@@ -4493,6 +4972,106 @@ var WorldOrbit = (() => {
4493
4972
  throw new WorldOrbitError(`Unknown relation field "${tokens[0].value}"`, line, tokens[0].column);
4494
4973
  }
4495
4974
  }
4975
+ function applyEventField(section, indent, tokens, line) {
4976
+ if (section.activePose && indent <= (section.poseIndent ?? 0)) {
4977
+ section.activePose = null;
4978
+ section.poseIndent = null;
4979
+ section.activePoseSeenFields.clear();
4980
+ }
4981
+ if (!section.activePose && section.inPositions && indent <= (section.positionsIndent ?? 0)) {
4982
+ section.inPositions = false;
4983
+ section.positionsIndent = null;
4984
+ }
4985
+ if (section.activePose) {
4986
+ section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
4987
+ return;
4988
+ }
4989
+ if (section.inPositions) {
4990
+ if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "pose") {
4991
+ throw new WorldOrbitError(`Unknown event positions field "${tokens[0].value}"`, line, tokens[0]?.column ?? 1);
4992
+ }
4993
+ const objectId = tokens[1].value;
4994
+ if (!objectId.trim()) {
4995
+ throw new WorldOrbitError("Event pose object id must not be empty", line, tokens[1].column);
4996
+ }
4997
+ const rawPose = {
4998
+ objectId,
4999
+ fields: [],
5000
+ location: { line, column: tokens[0].column }
5001
+ };
5002
+ section.rawPoses.push(rawPose);
5003
+ section.activePose = rawPose;
5004
+ section.poseIndent = indent;
5005
+ section.activePoseSeenFields = /* @__PURE__ */ new Set();
5006
+ return;
5007
+ }
5008
+ if (tokens.length === 1 && tokens[0].value.toLowerCase() === "positions") {
5009
+ if (section.seenFields.has("positions")) {
5010
+ throw new WorldOrbitError('Duplicate event field "positions"', line, tokens[0].column);
5011
+ }
5012
+ section.seenFields.add("positions");
5013
+ section.inPositions = true;
5014
+ section.positionsIndent = indent;
5015
+ return;
5016
+ }
5017
+ const key = requireUniqueField(tokens, section.seenFields, line);
5018
+ switch (key) {
5019
+ case "kind":
5020
+ section.event.kind = joinFieldValue(tokens, line);
5021
+ return;
5022
+ case "label":
5023
+ section.event.label = joinFieldValue(tokens, line);
5024
+ return;
5025
+ case "summary":
5026
+ section.event.summary = joinFieldValue(tokens, line);
5027
+ return;
5028
+ case "target":
5029
+ section.event.targetObjectId = joinFieldValue(tokens, line);
5030
+ return;
5031
+ case "participants":
5032
+ section.event.participantObjectIds = parseTokenList(tokens.slice(1), line, "participants");
5033
+ return;
5034
+ case "timing":
5035
+ section.event.timing = joinFieldValue(tokens, line);
5036
+ return;
5037
+ case "visibility":
5038
+ section.event.visibility = joinFieldValue(tokens, line);
5039
+ return;
5040
+ case "tags":
5041
+ section.event.tags = parseTokenList(tokens.slice(1), line, "tags");
5042
+ return;
5043
+ case "color":
5044
+ section.event.color = joinFieldValue(tokens, line);
5045
+ return;
5046
+ case "hidden":
5047
+ section.event.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
5048
+ line,
5049
+ column: tokens[0].column
5050
+ });
5051
+ return;
5052
+ default:
5053
+ throw new WorldOrbitError(`Unknown event field "${tokens[0].value}"`, line, tokens[0].column);
5054
+ }
5055
+ }
5056
+ function parseEventPoseField(tokens, line, seenFields) {
5057
+ if (tokens.length < 2) {
5058
+ throw new WorldOrbitError("Invalid event pose field line", line, tokens[0]?.column ?? 1);
5059
+ }
5060
+ const key = tokens[0].value;
5061
+ if (!EVENT_POSE_FIELD_KEYS.has(key)) {
5062
+ throw new WorldOrbitError(`Unknown event pose field "${key}"`, line, tokens[0].column);
5063
+ }
5064
+ if (seenFields.has(key)) {
5065
+ throw new WorldOrbitError(`Duplicate event pose field "${key}"`, line, tokens[0].column);
5066
+ }
5067
+ seenFields.add(key);
5068
+ return {
5069
+ type: "field",
5070
+ key,
5071
+ values: tokens.slice(1).map((token) => token.value),
5072
+ location: { line, column: tokens[0].column }
5073
+ };
5074
+ }
4496
5075
  function applyObjectField(section, indent, tokens, line) {
4497
5076
  if (section.activeBlock && indent <= (section.blockIndent ?? 0)) {
4498
5077
  section.activeBlock = null;
@@ -4551,7 +5130,7 @@ var WorldOrbit = (() => {
4551
5130
  function parseObjectTypeTokens(tokens, line) {
4552
5131
  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
5132
  }
4554
- function parseLayerTokens(tokens, line) {
5133
+ function parseLayerTokens(tokens, line, sourceSchemaVersion, diagnostics) {
4555
5134
  const layers = {};
4556
5135
  for (const token of parseTokenList(tokens, line, "layers")) {
4557
5136
  const enabled = !token.startsWith("-") && !token.startsWith("!");
@@ -4561,7 +5140,13 @@ var WorldOrbit = (() => {
4561
5140
  layers["orbits-front"] = enabled;
4562
5141
  continue;
4563
5142
  }
4564
- if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "objects" || raw === "labels" || raw === "metadata") {
5143
+ if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "events" || raw === "objects" || raw === "labels" || raw === "metadata") {
5144
+ if (raw === "events" && sourceSchemaVersion && diagnostics) {
5145
+ warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "layers.events", {
5146
+ line,
5147
+ column: tokens[0]?.column ?? 1
5148
+ });
5149
+ }
4565
5150
  layers[raw] = enabled;
4566
5151
  }
4567
5152
  }
@@ -4700,7 +5285,7 @@ var WorldOrbit = (() => {
4700
5285
  }
4701
5286
  function normalizeDraftObject(node, sourceSchemaVersion, diagnostics) {
4702
5287
  const fieldMap = collectDraftFields(node.fields);
4703
- const placement = extractDraftPlacement(node.objectType, fieldMap);
5288
+ const placement = extractPlacementFromFieldMap(fieldMap);
4704
5289
  const properties = normalizeDraftProperties(node.objectType, fieldMap);
4705
5290
  const groups = parseOptionalTokenList(fieldMap.get("groups")?.[0]);
4706
5291
  const epoch = parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]);
@@ -4752,6 +5337,24 @@ var WorldOrbit = (() => {
4752
5337
  }
4753
5338
  return object;
4754
5339
  }
5340
+ function normalizeDraftEvent(event, rawPoses) {
5341
+ return {
5342
+ ...event,
5343
+ participantObjectIds: [...new Set(event.participantObjectIds)],
5344
+ tags: [...new Set(event.tags)],
5345
+ positions: rawPoses.map((pose) => normalizeDraftEventPose(pose))
5346
+ };
5347
+ }
5348
+ function normalizeDraftEventPose(rawPose) {
5349
+ const fieldMap = collectDraftFields(rawPose.fields);
5350
+ const placement = extractPlacementFromFieldMap(fieldMap);
5351
+ return {
5352
+ objectId: rawPose.objectId,
5353
+ placement,
5354
+ inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
5355
+ outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer")
5356
+ };
5357
+ }
4755
5358
  function collectDraftFields(fields) {
4756
5359
  const grouped = /* @__PURE__ */ new Map();
4757
5360
  for (const field of fields) {
@@ -4768,7 +5371,7 @@ var WorldOrbit = (() => {
4768
5371
  }
4769
5372
  return grouped;
4770
5373
  }
4771
- function extractDraftPlacement(objectType, fieldMap) {
5374
+ function extractPlacementFromFieldMap(fieldMap) {
4772
5375
  const orbitField = fieldMap.get("orbit")?.[0];
4773
5376
  const atField = fieldMap.get("at")?.[0];
4774
5377
  const surfaceField = fieldMap.get("surface")?.[0];
@@ -5061,6 +5664,7 @@ var WorldOrbit = (() => {
5061
5664
  },
5062
5665
  groups: [],
5063
5666
  relations: [],
5667
+ events: [],
5064
5668
  objects: [],
5065
5669
  diagnostics: []
5066
5670
  };
@@ -5087,6 +5691,12 @@ var WorldOrbit = (() => {
5087
5691
  for (const relation of [...document2.relations].sort(compareIdLike2)) {
5088
5692
  paths.push({ kind: "relation", id: relation.id });
5089
5693
  }
5694
+ for (const event of [...document2.events].sort(compareIdLike2)) {
5695
+ paths.push({ kind: "event", id: event.id });
5696
+ for (const pose of [...event.positions].sort(comparePoseObjectId2)) {
5697
+ paths.push({ kind: "event-pose", id: event.id, key: pose.objectId });
5698
+ }
5699
+ }
5090
5700
  for (const object of [...document2.objects].sort(compareIdLike2)) {
5091
5701
  paths.push({ kind: "object", id: object.id });
5092
5702
  }
@@ -5102,6 +5712,10 @@ var WorldOrbit = (() => {
5102
5712
  return path.key ? document2.system?.atlasMetadata[path.key] ?? null : null;
5103
5713
  case "group":
5104
5714
  return path.id ? findGroup(document2, path.id) : null;
5715
+ case "event":
5716
+ return path.id ? findEvent(document2, path.id) : null;
5717
+ case "event-pose":
5718
+ return path.id && path.key ? findEventPose(document2, path.id, path.key) : null;
5105
5719
  case "object":
5106
5720
  return path.id ? findObject(document2, path.id) : null;
5107
5721
  case "viewpoint":
@@ -5141,6 +5755,18 @@ var WorldOrbit = (() => {
5141
5755
  }
5142
5756
  upsertById(next.groups, value);
5143
5757
  return next;
5758
+ case "event":
5759
+ if (!path.id) {
5760
+ throw new Error('Event updates require an "id" value.');
5761
+ }
5762
+ upsertById(next.events, value);
5763
+ return next;
5764
+ case "event-pose":
5765
+ if (!path.id || !path.key) {
5766
+ throw new Error('Event pose updates require an event "id" and pose "key" value.');
5767
+ }
5768
+ upsertEventPose(next.events, path.id, value);
5769
+ return next;
5144
5770
  case "object":
5145
5771
  if (!path.id) {
5146
5772
  throw new Error('Object updates require an "id" value.');
@@ -5189,6 +5815,19 @@ var WorldOrbit = (() => {
5189
5815
  next.groups = next.groups.filter((group) => group.id !== path.id);
5190
5816
  }
5191
5817
  return next;
5818
+ case "event":
5819
+ if (path.id) {
5820
+ next.events = next.events.filter((event) => event.id !== path.id);
5821
+ }
5822
+ return next;
5823
+ case "event-pose":
5824
+ if (path.id && path.key) {
5825
+ const event = findEvent(next, path.id);
5826
+ if (event) {
5827
+ event.positions = event.positions.filter((pose) => pose.objectId !== path.key);
5828
+ }
5829
+ }
5830
+ return next;
5192
5831
  case "viewpoint":
5193
5832
  if (path.id) {
5194
5833
  system.viewpoints = system.viewpoints.filter((viewpoint) => viewpoint.id !== path.id);
@@ -5257,6 +5896,22 @@ var WorldOrbit = (() => {
5257
5896
  };
5258
5897
  }
5259
5898
  }
5899
+ if (diagnostic.field?.startsWith("event.")) {
5900
+ const parts = diagnostic.field.split(".");
5901
+ if (parts[1] && findEvent(document2, parts[1])) {
5902
+ if (parts[2] === "pose" && parts[3] && findEventPose(document2, parts[1], parts[3])) {
5903
+ return {
5904
+ kind: "event-pose",
5905
+ id: parts[1],
5906
+ key: parts[3]
5907
+ };
5908
+ }
5909
+ return {
5910
+ kind: "event",
5911
+ id: parts[1]
5912
+ };
5913
+ }
5914
+ }
5260
5915
  if (diagnostic.field && diagnostic.field in ensureSystem(document2).atlasMetadata) {
5261
5916
  return {
5262
5917
  kind: "metadata",
@@ -5288,6 +5943,12 @@ var WorldOrbit = (() => {
5288
5943
  function findRelation(document2, relationId) {
5289
5944
  return document2.relations.find((relation) => relation.id === relationId) ?? null;
5290
5945
  }
5946
+ function findEvent(document2, eventId) {
5947
+ return document2.events.find((event) => event.id === eventId) ?? null;
5948
+ }
5949
+ function findEventPose(document2, eventId, objectId) {
5950
+ return findEvent(document2, eventId)?.positions.find((pose) => pose.objectId === objectId) ?? null;
5951
+ }
5291
5952
  function findViewpoint(system, viewpointId) {
5292
5953
  return system?.viewpoints.find((viewpoint) => viewpoint.id === viewpointId) ?? null;
5293
5954
  }
@@ -5303,9 +5964,25 @@ var WorldOrbit = (() => {
5303
5964
  }
5304
5965
  items[index] = value;
5305
5966
  }
5967
+ function upsertEventPose(events, eventId, value) {
5968
+ const event = events.find((entry) => entry.id === eventId);
5969
+ if (!event) {
5970
+ throw new Error(`Unknown event "${eventId}" for pose update.`);
5971
+ }
5972
+ const index = event.positions.findIndex((entry) => entry.objectId === value.objectId);
5973
+ if (index === -1) {
5974
+ event.positions.push(value);
5975
+ event.positions.sort(comparePoseObjectId2);
5976
+ return;
5977
+ }
5978
+ event.positions[index] = value;
5979
+ }
5306
5980
  function compareIdLike2(left, right) {
5307
5981
  return left.id.localeCompare(right.id);
5308
5982
  }
5983
+ function comparePoseObjectId2(left, right) {
5984
+ return left.objectId.localeCompare(right.objectId);
5985
+ }
5309
5986
 
5310
5987
  // packages/core/dist/load.js
5311
5988
  var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1)?$/i;
@@ -5540,6 +6217,7 @@ var WorldOrbit = (() => {
5540
6217
  background: true,
5541
6218
  guides: true,
5542
6219
  relations: true,
6220
+ events: true,
5543
6221
  orbits: true,
5544
6222
  objects: true,
5545
6223
  labels: true,
@@ -5691,12 +6369,14 @@ var WorldOrbit = (() => {
5691
6369
  return {
5692
6370
  version: "2.0",
5693
6371
  viewpointId,
6372
+ activeEventId: renderOptions.activeEventId ?? null,
5694
6373
  viewerState: { ...viewerState },
5695
6374
  renderOptions: {
5696
6375
  preset: renderOptions.preset,
5697
6376
  projection: renderOptions.projection,
5698
6377
  layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
5699
- scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0
6378
+ scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
6379
+ activeEventId: renderOptions.activeEventId ?? null
5700
6380
  },
5701
6381
  filter: normalizeViewerFilter(filter)
5702
6382
  };
@@ -5709,6 +6389,7 @@ var WorldOrbit = (() => {
5709
6389
  return {
5710
6390
  version: "2.0",
5711
6391
  viewpointId: raw.viewpointId ?? null,
6392
+ activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null,
5712
6393
  viewerState: {
5713
6394
  scale: raw.viewerState?.scale ?? 1,
5714
6395
  rotationDeg: raw.viewerState?.rotationDeg ?? 0,
@@ -5720,7 +6401,8 @@ var WorldOrbit = (() => {
5720
6401
  preset: raw.renderOptions?.preset,
5721
6402
  projection: raw.renderOptions?.projection,
5722
6403
  layers: raw.renderOptions?.layers ? { ...raw.renderOptions.layers } : void 0,
5723
- scaleModel: raw.renderOptions?.scaleModel ? { ...raw.renderOptions.scaleModel } : void 0
6404
+ scaleModel: raw.renderOptions?.scaleModel ? { ...raw.renderOptions.scaleModel } : void 0,
6405
+ activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null
5724
6406
  },
5725
6407
  filter: normalizeViewerFilter(raw.filter ?? null)
5726
6408
  };
@@ -5736,7 +6418,8 @@ var WorldOrbit = (() => {
5736
6418
  renderOptions: {
5737
6419
  ...atlasState.renderOptions,
5738
6420
  layers: atlasState.renderOptions.layers ? { ...atlasState.renderOptions.layers } : void 0,
5739
- scaleModel: atlasState.renderOptions.scaleModel ? { ...atlasState.renderOptions.scaleModel } : void 0
6421
+ scaleModel: atlasState.renderOptions.scaleModel ? { ...atlasState.renderOptions.scaleModel } : void 0,
6422
+ activeEventId: atlasState.renderOptions.activeEventId ?? null
5740
6423
  },
5741
6424
  filter: atlasState.filter ? { ...atlasState.filter } : null
5742
6425
  }
@@ -5754,6 +6437,7 @@ var WorldOrbit = (() => {
5754
6437
  background: viewpoint.layers.background,
5755
6438
  guides: viewpoint.layers.guides,
5756
6439
  relations: viewpoint.layers.relations,
6440
+ events: viewpoint.layers.events,
5757
6441
  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
6442
  objects: viewpoint.layers.objects,
5759
6443
  labels: viewpoint.layers.labels,
@@ -6040,6 +6724,7 @@ var WorldOrbit = (() => {
6040
6724
  const orbitMarkup = layers.orbits ? renderOrbitLayer(scene, visibleObjectIds, layers.structures) : { back: "", front: "" };
6041
6725
  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
6726
  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("") : "";
6727
+ const eventMarkup = layers.events ? scene.events.filter((event) => !event.hidden).map((event) => renderSceneEventOverlay(scene, event, visibleObjectIds, theme)).join("") : "";
6043
6728
  const objectMarkup = layers.objects ? visibleObjects.map((object) => renderSceneObject(object, options.selectedObjectId ?? null, theme)).join("") : "";
6044
6729
  const labelMarkup = layers.labels ? visibleLabels.map((label) => renderSceneLabel(scene, label, options.selectedObjectId ?? null)).join("") : "";
6045
6730
  const metadataMarkup = layers.metadata ? `<text class="wo-title" x="56" y="64">${escapeXml(scene.title)}</text>
@@ -6075,6 +6760,9 @@ var WorldOrbit = (() => {
6075
6760
  .wo-orbit-front { opacity: 0.9; }
6076
6761
  .wo-orbit-band { stroke: ${theme.orbitBand}; stroke-linecap: round; }
6077
6762
  .wo-relation { stroke: ${theme.relation}; stroke-width: 2; stroke-dasharray: 10 6; }
6763
+ .wo-event-line { stroke: ${theme.accent}; stroke-width: 1.6; stroke-dasharray: 5 5; opacity: 0.72; }
6764
+ .wo-event-node { fill: ${theme.accent}; stroke: ${theme.selected}; stroke-width: 1.4; opacity: 0.92; }
6765
+ .wo-event-label { fill: ${theme.accent}; font-family: ${theme.fontFamily}; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
6078
6766
  .wo-leader { stroke: ${theme.leader}; stroke-width: 1.5; stroke-dasharray: 6 5; }
6079
6767
  .wo-label { fill: ${theme.ink}; font-family: ${theme.fontFamily}; font-weight: 600; letter-spacing: 0.02em; }
6080
6768
  .wo-label-secondary { fill: ${theme.muted}; font-family: ${theme.fontFamily}; font-weight: 500; }
@@ -6109,6 +6797,7 @@ var WorldOrbit = (() => {
6109
6797
  ${layers.orbits ? `<g data-layer-id="orbits-back">${orbitMarkup.back}</g>` : ""}
6110
6798
  ${layers.guides ? `<g data-layer-id="guides">${leaderMarkup}</g>` : ""}
6111
6799
  ${layers.relations ? `<g data-layer-id="relations">${relationMarkup}</g>` : ""}
6800
+ ${layers.events ? `<g data-layer-id="events">${eventMarkup}</g>` : ""}
6112
6801
  ${layers.objects ? `<g data-layer-id="objects">${objectMarkup}</g>` : ""}
6113
6802
  ${layers.orbits ? `<g data-layer-id="orbits-front">${orbitMarkup.front}</g>` : ""}
6114
6803
  ${layers.labels ? `<g data-layer-id="labels">${labelMarkup}</g>` : ""}
@@ -6116,6 +6805,20 @@ var WorldOrbit = (() => {
6116
6805
  </g>
6117
6806
  </g>
6118
6807
  </svg>`;
6808
+ }
6809
+ function renderSceneEventOverlay(scene, event, visibleObjectIds, theme) {
6810
+ const participants = event.objectIds.filter((objectId) => visibleObjectIds.has(objectId)).map((objectId) => scene.objects.find((object) => object.objectId === objectId && !object.hidden)).filter(Boolean);
6811
+ if (participants.length === 0) {
6812
+ return "";
6813
+ }
6814
+ const stroke = event.event.color || theme.accent;
6815
+ const label = event.event.label || event.event.id;
6816
+ 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("");
6817
+ return `<g class="wo-event" data-render-id="${escapeXml(event.renderId)}" data-event-id="${escapeAttribute(event.eventId)}">
6818
+ ${lineMarkup}
6819
+ <circle class="wo-event-node" cx="${event.x}" cy="${event.y}" r="5" fill="${escapeAttribute(stroke)}" />
6820
+ <text class="wo-event-label" x="${event.x}" y="${event.y - 10}" text-anchor="middle" font-size="10">${escapeXml(label)}</text>
6821
+ </g>`;
6119
6822
  }
6120
6823
  function renderDocumentToSvg(document2, options = {}) {
6121
6824
  return renderSceneToSvg(renderDocumentToScene(document2, options), options);
@@ -6715,6 +7418,13 @@ var WorldOrbit = (() => {
6715
7418
  value: `${details.object.resonance.targetObjectId} ${details.object.resonance.ratio}`
6716
7419
  });
6717
7420
  }
7421
+ if (details.relatedEvents.length > 0) {
7422
+ fields.set("events", {
7423
+ key: "events",
7424
+ label: "Events",
7425
+ value: details.relatedEvents.map((event) => event.event.label || event.event.id).join(", ")
7426
+ });
7427
+ }
6718
7428
  if (placement?.mode === "at") {
6719
7429
  fields.set("placement", {
6720
7430
  key: "placement",
@@ -7129,6 +7839,12 @@ var WorldOrbit = (() => {
7129
7839
  emitAtlasStateChange();
7130
7840
  return true;
7131
7841
  },
7842
+ getActiveEventId() {
7843
+ return renderOptions.activeEventId ?? null;
7844
+ },
7845
+ setActiveEvent(id) {
7846
+ api.setRenderOptions({ activeEventId: id });
7847
+ },
7132
7848
  search(query, limit = 12) {
7133
7849
  return searchSceneObjects(scene, query, limit);
7134
7850
  },
@@ -7393,6 +8109,7 @@ var WorldOrbit = (() => {
7393
8109
  orbit: scene.orbitVisuals.find((orbit) => orbit.objectId === renderObject.objectId && !orbit.hidden) ?? null,
7394
8110
  relatedOrbits: scene.orbitVisuals.filter((orbit) => !orbit.hidden && (orbit.objectId === renderObject.objectId || renderObject.ancestorIds.includes(orbit.objectId) || renderObject.childIds.includes(orbit.objectId))),
7395
8111
  relations: scene.relations.filter((relation) => !relation.hidden && (relation.fromObjectId === renderObject.objectId || relation.toObjectId === renderObject.objectId)),
8112
+ relatedEvents: scene.events.filter((event) => !event.hidden && (event.targetObjectId === renderObject.objectId || event.objectIds.includes(renderObject.objectId))),
7396
8113
  parent: getObjectById(renderObject.parentId),
7397
8114
  children: renderObject.childIds.map((childId) => getObjectById(childId)).filter(Boolean),
7398
8115
  ancestors: renderObject.ancestorIds.map((ancestorId) => getObjectById(ancestorId)).filter(Boolean),
@@ -7709,7 +8426,8 @@ var WorldOrbit = (() => {
7709
8426
  filter: renderOptions.filter ? { ...renderOptions.filter } : void 0,
7710
8427
  scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
7711
8428
  layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
7712
- theme: renderOptions.theme && typeof renderOptions.theme === "object" ? { ...renderOptions.theme } : renderOptions.theme
8429
+ theme: renderOptions.theme && typeof renderOptions.theme === "object" ? { ...renderOptions.theme } : renderOptions.theme,
8430
+ activeEventId: renderOptions.activeEventId ?? null
7713
8431
  };
7714
8432
  }
7715
8433
  function mergeRenderOptions(current, next) {
@@ -7729,7 +8447,7 @@ var WorldOrbit = (() => {
7729
8447
  };
7730
8448
  }
7731
8449
  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;
8450
+ return options.width !== void 0 || options.height !== void 0 || options.padding !== void 0 || options.preset !== void 0 || options.projection !== void 0 || options.scaleModel !== void 0 || options.activeEventId !== void 0;
7733
8451
  }
7734
8452
  function resolveSourceRenderOptions2(loaded, renderOptions) {
7735
8453
  const atlasDocument = loaded.atlasDocument ?? loaded.draftDocument;
@@ -8339,6 +9057,7 @@ var WorldOrbit = (() => {
8339
9057
  groupCount: activeViewer.getScene().groups.length,
8340
9058
  semanticGroupCount: activeViewer.getScene().semanticGroups.length,
8341
9059
  relationCount: activeViewer.getScene().relations.length,
9060
+ eventCount: activeViewer.getScene().events.length,
8342
9061
  viewpointCount: activeViewer.getScene().viewpoints.length
8343
9062
  }
8344
9063
  };