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
@@ -565,6 +565,7 @@ var WorldOrbit = (() => {
565
565
  system,
566
566
  groups: [],
567
567
  relations: [],
568
+ events: [],
568
569
  objects
569
570
  };
570
571
  }
@@ -944,12 +945,16 @@ var WorldOrbit = (() => {
944
945
  const height = frame.height;
945
946
  const padding = frame.padding;
946
947
  const layoutPreset = resolveLayoutPreset(document2);
947
- const projection = resolveProjection(document2, options.projection);
948
+ const schemaProjection = resolveProjection(document2, options.projection);
949
+ const camera = normalizeViewCamera(options.camera ?? null);
950
+ const renderProjection = resolveRenderProjection(schemaProjection, camera);
948
951
  const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
949
952
  const spacingFactor = layoutPresetSpacing(layoutPreset);
950
953
  const systemId = document2.system?.id ?? null;
951
- const objectMap = new Map(document2.objects.map((object) => [object.id, object]));
952
- const relationships = buildSceneRelationships(document2.objects, objectMap);
954
+ const activeEventId = options.activeEventId ?? null;
955
+ const effectiveObjects = createEffectiveObjects(document2.objects, document2.events ?? [], activeEventId);
956
+ const objectMap = new Map(effectiveObjects.map((object) => [object.id, object]));
957
+ const relationships = buildSceneRelationships(effectiveObjects, objectMap);
953
958
  const positions = /* @__PURE__ */ new Map();
954
959
  const orbitDrafts = [];
955
960
  const leaderDrafts = [];
@@ -958,7 +963,7 @@ var WorldOrbit = (() => {
958
963
  const atObjects = [];
959
964
  const surfaceChildren = /* @__PURE__ */ new Map();
960
965
  const orbitChildren = /* @__PURE__ */ new Map();
961
- for (const object of document2.objects) {
966
+ for (const object of effectiveObjects) {
962
967
  const placement = object.placement;
963
968
  if (!placement) {
964
969
  rootObjects.push(object);
@@ -985,7 +990,7 @@ var WorldOrbit = (() => {
985
990
  surfaceChildren,
986
991
  objectMap,
987
992
  spacingFactor,
988
- projection,
993
+ projection: renderProjection,
989
994
  scaleModel
990
995
  };
991
996
  const primaryRoot = rootObjects.find((object) => object.type === "star") ?? rootObjects[0] ?? null;
@@ -997,7 +1002,7 @@ var WorldOrbit = (() => {
997
1002
  const rootRingRadius = Math.min(width, height) * 0.28 * spacingFactor * scaleModel.orbitDistanceMultiplier;
998
1003
  secondaryRoots.forEach((object, index) => {
999
1004
  const angle = angleForIndex(index, secondaryRoots.length, -Math.PI / 2);
1000
- const offset = projectPolarOffset(angle, rootRingRadius, projection, 1);
1005
+ const offset = projectPolarOffset(angle, rootRingRadius, renderProjection, 1);
1001
1006
  placeObject(object, centerX + offset.x, centerY + offset.y, 0, positions, orbitDrafts, leaderDrafts, context);
1002
1007
  });
1003
1008
  }
@@ -1053,38 +1058,48 @@ var WorldOrbit = (() => {
1053
1058
  const objects = [...positions.values()].map((position) => createSceneObject(position, scaleModel, relationships));
1054
1059
  const orbitVisuals = orbitDrafts.map((draft) => createOrbitVisual(draft, relationships.groupIds.get(draft.object.id) ?? null));
1055
1060
  const leaders = leaderDrafts.map((draft) => createLeaderLine(draft));
1056
- const labels = createSceneLabels(objects, height, scaleModel.labelMultiplier);
1061
+ const labels = createSceneLabels(objects, width, height, scaleModel.labelMultiplier);
1057
1062
  const relations = createSceneRelations(document2, objects);
1058
- const layers = createSceneLayers(orbitVisuals, relations, leaders, objects, labels);
1059
- const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships);
1063
+ const events = createSceneEvents(document2.events ?? [], objects, activeEventId);
1064
+ const layers = createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels);
1065
+ const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, scaleModel.labelMultiplier);
1060
1066
  const semanticGroups = createSceneSemanticGroups(document2, objects);
1061
- const viewpoints = createSceneViewpoints(document2, projection, frame.preset, relationships, objectMap);
1062
- const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels);
1067
+ const viewpoints = createSceneViewpoints(document2, schemaProjection, frame.preset, relationships, objectMap);
1068
+ const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, scaleModel.labelMultiplier);
1063
1069
  return {
1064
1070
  width,
1065
1071
  height,
1066
1072
  padding,
1067
1073
  renderPreset: frame.preset,
1068
- projection,
1074
+ projection: schemaProjection,
1075
+ renderProjection,
1076
+ camera,
1069
1077
  scaleModel,
1070
1078
  title: String(document2.system?.title ?? document2.system?.properties.title ?? document2.system?.id ?? "WorldOrbit") || "WorldOrbit",
1071
- subtitle: `${capitalizeLabel(projection)} view - ${capitalizeLabel(layoutPreset)} layout`,
1079
+ subtitle: buildSceneSubtitle(schemaProjection, renderProjection, layoutPreset, camera),
1072
1080
  systemId,
1073
- viewMode: projection,
1081
+ viewMode: schemaProjection,
1074
1082
  layoutPreset,
1075
1083
  metadata: {
1076
1084
  format: document2.format,
1077
1085
  version: document2.version,
1078
- view: projection,
1086
+ view: schemaProjection,
1087
+ renderProjection,
1079
1088
  scale: String(document2.system?.properties.scale ?? layoutPreset),
1080
1089
  units: String(document2.system?.properties.units ?? "mixed"),
1081
- preset: frame.preset ?? "custom"
1090
+ preset: frame.preset ?? "custom",
1091
+ ...camera?.azimuth !== null ? { "camera.azimuth": String(camera?.azimuth) } : {},
1092
+ ...camera?.elevation !== null ? { "camera.elevation": String(camera?.elevation) } : {},
1093
+ ...camera?.roll !== null ? { "camera.roll": String(camera?.roll) } : {},
1094
+ ...camera?.distance !== null ? { "camera.distance": String(camera?.distance) } : {}
1082
1095
  },
1083
1096
  contentBounds,
1084
1097
  layers,
1085
1098
  groups,
1086
1099
  semanticGroups,
1087
1100
  viewpoints,
1101
+ events,
1102
+ activeEventId,
1088
1103
  objects,
1089
1104
  orbitVisuals,
1090
1105
  relations,
@@ -1103,6 +1118,56 @@ var WorldOrbit = (() => {
1103
1118
  y: center.y + dx * sin + dy * cos
1104
1119
  };
1105
1120
  }
1121
+ function createEffectiveObjects(objects, events, activeEventId) {
1122
+ const cloned = objects.map((object) => structuredClone(object));
1123
+ if (!activeEventId) {
1124
+ return cloned;
1125
+ }
1126
+ const activeEvent = events.find((event) => event.id === activeEventId);
1127
+ if (!activeEvent) {
1128
+ return cloned;
1129
+ }
1130
+ const objectMap = new Map(cloned.map((object) => [object.id, object]));
1131
+ const referencedIds = /* @__PURE__ */ new Set([
1132
+ ...activeEvent.targetObjectId ? [activeEvent.targetObjectId] : [],
1133
+ ...activeEvent.participantObjectIds,
1134
+ ...activeEvent.positions.map((pose) => pose.objectId)
1135
+ ]);
1136
+ for (const objectId of referencedIds) {
1137
+ const object = objectMap.get(objectId);
1138
+ if (!object) {
1139
+ continue;
1140
+ }
1141
+ if (activeEvent.epoch) {
1142
+ object.epoch = activeEvent.epoch;
1143
+ }
1144
+ if (activeEvent.referencePlane) {
1145
+ object.referencePlane = activeEvent.referencePlane;
1146
+ }
1147
+ }
1148
+ for (const pose of activeEvent.positions) {
1149
+ const object = objectMap.get(pose.objectId);
1150
+ if (!object) {
1151
+ continue;
1152
+ }
1153
+ if (pose.placement) {
1154
+ object.placement = structuredClone(pose.placement);
1155
+ }
1156
+ if (pose.inner) {
1157
+ object.properties.inner = { ...pose.inner };
1158
+ }
1159
+ if (pose.outer) {
1160
+ object.properties.outer = { ...pose.outer };
1161
+ }
1162
+ if (pose.epoch) {
1163
+ object.epoch = pose.epoch;
1164
+ }
1165
+ if (pose.referencePlane) {
1166
+ object.referencePlane = pose.referencePlane;
1167
+ }
1168
+ }
1169
+ return cloned;
1170
+ }
1106
1171
  function resolveLayoutPreset(document2) {
1107
1172
  const rawScale = String(document2.system?.properties.scale ?? "balanced").toLowerCase();
1108
1173
  switch (rawScale) {
@@ -1139,10 +1204,59 @@ var WorldOrbit = (() => {
1139
1204
  }
1140
1205
  }
1141
1206
  function resolveProjection(document2, projection) {
1142
- if (projection === "topdown" || projection === "isometric") {
1207
+ if (projection === "topdown" || projection === "isometric" || projection === "orthographic" || projection === "perspective") {
1143
1208
  return projection;
1144
1209
  }
1145
- return String(document2.system?.properties.view ?? "topdown").toLowerCase() === "isometric" ? "isometric" : "topdown";
1210
+ const documentView = String(document2.system?.properties.view ?? "topdown").toLowerCase();
1211
+ return parseViewProjection(documentView) ?? "topdown";
1212
+ }
1213
+ function resolveRenderProjection(projection, camera) {
1214
+ switch (projection) {
1215
+ case "topdown":
1216
+ return "topdown";
1217
+ case "isometric":
1218
+ return "isometric";
1219
+ case "orthographic":
1220
+ return camera && (camera.azimuth !== null || camera.elevation !== null || camera.roll !== null) ? "isometric" : "topdown";
1221
+ case "perspective":
1222
+ return "isometric";
1223
+ }
1224
+ }
1225
+ function normalizeViewCamera(camera) {
1226
+ if (!camera) {
1227
+ return null;
1228
+ }
1229
+ const normalized = {
1230
+ azimuth: normalizeFiniteCameraValue(camera.azimuth),
1231
+ elevation: normalizeFiniteCameraValue(camera.elevation),
1232
+ roll: normalizeFiniteCameraValue(camera.roll),
1233
+ distance: normalizePositiveCameraDistance(camera.distance)
1234
+ };
1235
+ return normalized.azimuth !== null || normalized.elevation !== null || normalized.roll !== null || normalized.distance !== null ? normalized : null;
1236
+ }
1237
+ function normalizeFiniteCameraValue(value) {
1238
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1239
+ }
1240
+ function normalizePositiveCameraDistance(value) {
1241
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
1242
+ }
1243
+ function buildSceneSubtitle(projection, renderProjection, layoutPreset, camera) {
1244
+ const parts = [`${capitalizeLabel(projection)} view`, `${capitalizeLabel(layoutPreset)} layout`];
1245
+ if (projection !== renderProjection) {
1246
+ parts.push(`2D ${renderProjection} fallback`);
1247
+ }
1248
+ if (camera) {
1249
+ const cameraParts = [
1250
+ camera.azimuth !== null ? `az ${camera.azimuth}` : null,
1251
+ camera.elevation !== null ? `el ${camera.elevation}` : null,
1252
+ camera.roll !== null ? `roll ${camera.roll}` : null,
1253
+ camera.distance !== null ? `dist ${camera.distance}` : null
1254
+ ].filter(Boolean);
1255
+ if (cameraParts.length > 0) {
1256
+ parts.push(`camera ${cameraParts.join(" / ")}`);
1257
+ }
1258
+ }
1259
+ return parts.join(" - ");
1146
1260
  }
1147
1261
  function resolveScaleModel(layoutPreset, overrides) {
1148
1262
  const defaults = defaultScaleModel(layoutPreset);
@@ -1258,24 +1372,14 @@ var WorldOrbit = (() => {
1258
1372
  hidden: draft.object.properties.hidden === true
1259
1373
  };
1260
1374
  }
1261
- function createSceneLabels(objects, sceneHeight, labelMultiplier) {
1375
+ function createSceneLabels(objects, sceneWidth, sceneHeight, labelMultiplier) {
1262
1376
  const labels = [];
1263
1377
  const occupied = [];
1264
- const visibleObjects = [...objects].filter((object) => !object.hidden && object.object.renderHints?.renderLabel !== false).sort((left, right) => left.sortKey - right.sortKey);
1378
+ const objectMap = new Map(objects.map((object) => [object.objectId, object]));
1379
+ const visibleObjects = [...objects].filter((object) => !object.hidden && object.object.renderHints?.renderLabel !== false).sort(compareLabelPlacementOrder);
1265
1380
  for (const object of visibleObjects) {
1266
- const direction = object.y > sceneHeight * 0.62 ? -1 : 1;
1267
- const labelHalfWidth = estimateLabelHalfWidth(object, labelMultiplier);
1268
- let labelY = object.y + direction * (object.radius + 18 * labelMultiplier);
1269
- let secondaryY = labelY + direction * (16 * labelMultiplier);
1270
- let bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
1271
- let attempts = 0;
1272
- while (occupied.some((entry) => rectsOverlap(entry, bounds)) && attempts < 10) {
1273
- labelY += direction * 14 * labelMultiplier;
1274
- secondaryY += direction * 14 * labelMultiplier;
1275
- bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
1276
- attempts += 1;
1277
- }
1278
- occupied.push(bounds);
1381
+ const placement = selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) ?? createLabelPlacement(object, defaultVerticalDirection(object, objectMap.get(object.parentId ?? "") ?? null, sceneHeight), 0, labelMultiplier);
1382
+ occupied.push(createLabelRect(object, placement, labelMultiplier));
1279
1383
  labels.push({
1280
1384
  renderId: `${object.renderId}-label`,
1281
1385
  objectId: object.objectId,
@@ -1284,17 +1388,128 @@ var WorldOrbit = (() => {
1284
1388
  semanticGroupIds: [...object.semanticGroupIds],
1285
1389
  label: object.label,
1286
1390
  secondaryLabel: object.secondaryLabel,
1287
- x: object.x,
1288
- y: labelY,
1289
- secondaryY,
1290
- textAnchor: "middle",
1291
- direction: direction < 0 ? "above" : "below",
1391
+ x: placement.x,
1392
+ y: placement.labelY,
1393
+ secondaryY: placement.secondaryY,
1394
+ textAnchor: placement.textAnchor,
1395
+ direction: placement.direction,
1292
1396
  hidden: object.hidden
1293
1397
  });
1294
1398
  }
1295
1399
  return labels;
1296
1400
  }
1297
- function createSceneLayers(orbitVisuals, relations, leaders, objects, labels) {
1401
+ function compareLabelPlacementOrder(left, right) {
1402
+ const priorityDiff = labelPlacementPriority(left) - labelPlacementPriority(right);
1403
+ if (priorityDiff !== 0) {
1404
+ return priorityDiff;
1405
+ }
1406
+ const renderPriorityDiff = (right.object.renderHints?.renderPriority ?? 0) - (left.object.renderHints?.renderPriority ?? 0);
1407
+ if (renderPriorityDiff !== 0) {
1408
+ return renderPriorityDiff;
1409
+ }
1410
+ return left.sortKey - right.sortKey;
1411
+ }
1412
+ function labelPlacementPriority(object) {
1413
+ switch (object.object.type) {
1414
+ case "star":
1415
+ return 0;
1416
+ case "planet":
1417
+ return 1;
1418
+ case "moon":
1419
+ return 2;
1420
+ case "belt":
1421
+ case "ring":
1422
+ return 3;
1423
+ case "asteroid":
1424
+ case "comet":
1425
+ return 4;
1426
+ case "structure":
1427
+ case "phenomenon":
1428
+ return 5;
1429
+ }
1430
+ }
1431
+ function selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) {
1432
+ for (const direction of preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight)) {
1433
+ const maxAttempts = direction === "left" || direction === "right" ? 4 : 6;
1434
+ for (let attempt = 0; attempt <= maxAttempts; attempt += 1) {
1435
+ const placement = createLabelPlacement(object, direction, attempt, labelMultiplier);
1436
+ const rect = createLabelRect(object, placement, labelMultiplier);
1437
+ if (!occupied.some((entry) => rectsOverlap(entry, rect))) {
1438
+ return placement;
1439
+ }
1440
+ }
1441
+ }
1442
+ return null;
1443
+ }
1444
+ function preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight) {
1445
+ const parent = object.parentId ? objectMap.get(object.parentId) ?? null : null;
1446
+ const vertical = defaultVerticalDirection(object, parent, sceneHeight);
1447
+ const oppositeVertical = vertical === "below" ? "above" : "below";
1448
+ const horizontal = defaultHorizontalDirection(object, parent, sceneWidth);
1449
+ const oppositeHorizontal = horizontal === "right" ? "left" : "right";
1450
+ 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";
1451
+ return preferHorizontal ? [horizontal, vertical, oppositeHorizontal, oppositeVertical] : [vertical, horizontal, oppositeVertical, oppositeHorizontal];
1452
+ }
1453
+ function defaultVerticalDirection(object, parent, sceneHeight) {
1454
+ if (parent && Math.abs(object.y - parent.y) > 6) {
1455
+ return object.y >= parent.y ? "below" : "above";
1456
+ }
1457
+ return object.y > sceneHeight * 0.62 ? "above" : "below";
1458
+ }
1459
+ function defaultHorizontalDirection(object, parent, sceneWidth) {
1460
+ if (parent && Math.abs(object.x - parent.x) > 6) {
1461
+ return object.x >= parent.x ? "right" : "left";
1462
+ }
1463
+ return object.x >= sceneWidth / 2 ? "right" : "left";
1464
+ }
1465
+ function createLabelPlacement(object, direction, attempt, labelMultiplier) {
1466
+ const step = 14 * labelMultiplier;
1467
+ switch (direction) {
1468
+ case "above": {
1469
+ const labelY = object.y - (object.radius + 18 * labelMultiplier + attempt * step);
1470
+ return {
1471
+ x: object.x,
1472
+ labelY,
1473
+ secondaryY: labelY - 16 * labelMultiplier,
1474
+ textAnchor: "middle",
1475
+ direction
1476
+ };
1477
+ }
1478
+ case "below": {
1479
+ const labelY = object.y + object.radius + 18 * labelMultiplier + attempt * step;
1480
+ return {
1481
+ x: object.x,
1482
+ labelY,
1483
+ secondaryY: labelY + 16 * labelMultiplier,
1484
+ textAnchor: "middle",
1485
+ direction
1486
+ };
1487
+ }
1488
+ case "left": {
1489
+ const x = object.x - (object.visualRadius + 16 * labelMultiplier + attempt * step);
1490
+ const labelY = object.y - 4 * labelMultiplier;
1491
+ return {
1492
+ x,
1493
+ labelY,
1494
+ secondaryY: labelY + 16 * labelMultiplier,
1495
+ textAnchor: "end",
1496
+ direction
1497
+ };
1498
+ }
1499
+ case "right": {
1500
+ const x = object.x + object.visualRadius + 16 * labelMultiplier + attempt * step;
1501
+ const labelY = object.y - 4 * labelMultiplier;
1502
+ return {
1503
+ x,
1504
+ labelY,
1505
+ secondaryY: labelY + 16 * labelMultiplier,
1506
+ textAnchor: "start",
1507
+ direction
1508
+ };
1509
+ }
1510
+ }
1511
+ }
1512
+ function createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels) {
1298
1513
  const backOrbitIds = orbitVisuals.filter((visual) => !visual.hidden && Boolean(visual.backArcPath)).map((visual) => visual.renderId);
1299
1514
  const frontOrbitIds = orbitVisuals.filter((visual) => !visual.hidden).map((visual) => visual.renderId);
1300
1515
  return [
@@ -1309,6 +1524,10 @@ var WorldOrbit = (() => {
1309
1524
  id: "relations",
1310
1525
  renderIds: relations.filter((relation) => !relation.hidden).map((relation) => relation.renderId)
1311
1526
  },
1527
+ {
1528
+ id: "events",
1529
+ renderIds: events.filter((event) => !event.hidden).map((event) => event.renderId)
1530
+ },
1312
1531
  {
1313
1532
  id: "objects",
1314
1533
  renderIds: objects.filter((object) => !object.hidden).map((object) => object.renderId)
@@ -1320,7 +1539,7 @@ var WorldOrbit = (() => {
1320
1539
  { id: "metadata", renderIds: ["wo-title", "wo-subtitle", "wo-meta"] }
1321
1540
  ];
1322
1541
  }
1323
- function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships) {
1542
+ function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, labelMultiplier) {
1324
1543
  const groups = /* @__PURE__ */ new Map();
1325
1544
  const ensureGroup = (groupId) => {
1326
1545
  if (!groupId) {
@@ -1369,7 +1588,7 @@ var WorldOrbit = (() => {
1369
1588
  }
1370
1589
  }
1371
1590
  for (const group of groups.values()) {
1372
- group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels);
1591
+ group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier);
1373
1592
  }
1374
1593
  return [...groups.values()].sort((left, right) => left.label.localeCompare(right.label));
1375
1594
  }
@@ -1403,6 +1622,29 @@ var WorldOrbit = (() => {
1403
1622
  };
1404
1623
  }).sort((left, right) => left.relation.id.localeCompare(right.relation.id));
1405
1624
  }
1625
+ function createSceneEvents(events, objects, activeEventId) {
1626
+ const objectMap = new Map(objects.map((object) => [object.objectId, object]));
1627
+ return events.map((event) => {
1628
+ const objectIds = [.../* @__PURE__ */ new Set([
1629
+ ...event.targetObjectId ? [event.targetObjectId] : [],
1630
+ ...event.participantObjectIds
1631
+ ])];
1632
+ const positions = objectIds.map((objectId) => objectMap.get(objectId)).filter(Boolean);
1633
+ const centroidX = positions.length > 0 ? positions.reduce((sum, object) => sum + object.x, 0) / positions.length : 0;
1634
+ const centroidY = positions.length > 0 ? positions.reduce((sum, object) => sum + object.y, 0) / positions.length : 0;
1635
+ return {
1636
+ renderId: `${createRenderId(event.id)}-event`,
1637
+ eventId: event.id,
1638
+ event,
1639
+ objectIds,
1640
+ participantIds: [...event.participantObjectIds],
1641
+ targetObjectId: event.targetObjectId,
1642
+ x: centroidX,
1643
+ y: centroidY,
1644
+ hidden: event.hidden || positions.length === 0 || positions.every((object) => object.hidden) || activeEventId !== null && event.id !== activeEventId
1645
+ };
1646
+ }).sort((left, right) => left.event.id.localeCompare(right.event.id));
1647
+ }
1406
1648
  function createSceneViewpoints(document2, projection, preset, relationships, objectMap) {
1407
1649
  const generatedOverview = createGeneratedOverviewViewpoint(document2, projection, preset);
1408
1650
  const drafts = /* @__PURE__ */ new Map();
@@ -1450,13 +1692,18 @@ var WorldOrbit = (() => {
1450
1692
  function createGeneratedOverviewViewpoint(document2, projection, preset) {
1451
1693
  const title = document2.system?.title ?? document2.system?.properties.title;
1452
1694
  const label = title ? `${String(title)} Overview` : "Overview";
1695
+ const camera = normalizeViewCamera(null);
1696
+ const renderProjection = resolveRenderProjection(projection, camera);
1453
1697
  return {
1454
1698
  id: "overview",
1455
1699
  label,
1456
1700
  summary: "Fit the whole system with the current atlas defaults.",
1457
1701
  objectId: null,
1458
1702
  selectedObjectId: null,
1703
+ eventIds: [],
1459
1704
  projection,
1705
+ renderProjection,
1706
+ camera,
1460
1707
  preset,
1461
1708
  rotationDeg: 0,
1462
1709
  scale: null,
@@ -1492,6 +1739,9 @@ var WorldOrbit = (() => {
1492
1739
  draft.select = normalizedValue;
1493
1740
  }
1494
1741
  return;
1742
+ case "events":
1743
+ draft.eventIds = splitListValue(normalizedValue);
1744
+ return;
1495
1745
  case "projection":
1496
1746
  case "view":
1497
1747
  draft.projection = parseViewProjection(normalizedValue) ?? projection;
@@ -1503,6 +1753,30 @@ var WorldOrbit = (() => {
1503
1753
  case "angle":
1504
1754
  draft.rotationDeg = parseFiniteNumber(normalizedValue) ?? draft.rotationDeg ?? 0;
1505
1755
  return;
1756
+ case "camera.azimuth":
1757
+ draft.camera = {
1758
+ ...draft.camera ?? createEmptyViewCamera(),
1759
+ azimuth: parseFiniteNumber(normalizedValue)
1760
+ };
1761
+ return;
1762
+ case "camera.elevation":
1763
+ draft.camera = {
1764
+ ...draft.camera ?? createEmptyViewCamera(),
1765
+ elevation: parseFiniteNumber(normalizedValue)
1766
+ };
1767
+ return;
1768
+ case "camera.roll":
1769
+ draft.camera = {
1770
+ ...draft.camera ?? createEmptyViewCamera(),
1771
+ roll: parseFiniteNumber(normalizedValue)
1772
+ };
1773
+ return;
1774
+ case "camera.distance":
1775
+ draft.camera = {
1776
+ ...draft.camera ?? createEmptyViewCamera(),
1777
+ distance: parsePositiveNumber(normalizedValue)
1778
+ };
1779
+ return;
1506
1780
  case "zoom":
1507
1781
  case "scale":
1508
1782
  draft.scale = parsePositiveNumber(normalizedValue);
@@ -1542,13 +1816,19 @@ var WorldOrbit = (() => {
1542
1816
  const selectedObjectId = draft.select && objectMap.has(draft.select) ? draft.select : objectId;
1543
1817
  const filter = normalizeViewpointFilter(draft.filter);
1544
1818
  const label = draft.label?.trim() || humanizeIdentifier(draft.id);
1819
+ const resolvedProjection = draft.projection ?? projection;
1820
+ const camera = normalizeViewCamera(draft.camera ?? null);
1821
+ const renderProjection = resolveRenderProjection(resolvedProjection, camera);
1545
1822
  return {
1546
1823
  id: draft.id,
1547
1824
  label,
1548
1825
  summary: draft.summary?.trim() || createViewpointSummary(label, objectId, filter),
1549
1826
  objectId,
1550
1827
  selectedObjectId,
1551
- projection: draft.projection ?? projection,
1828
+ eventIds: [...new Set(draft.eventIds ?? [])],
1829
+ projection: resolvedProjection,
1830
+ renderProjection,
1831
+ camera,
1552
1832
  preset: draft.preset ?? preset,
1553
1833
  rotationDeg: draft.rotationDeg ?? 0,
1554
1834
  scale: draft.scale ?? null,
@@ -1565,6 +1845,14 @@ var WorldOrbit = (() => {
1565
1845
  groupIds: []
1566
1846
  };
1567
1847
  }
1848
+ function createEmptyViewCamera() {
1849
+ return {
1850
+ azimuth: null,
1851
+ elevation: null,
1852
+ roll: null,
1853
+ distance: null
1854
+ };
1855
+ }
1568
1856
  function normalizeViewpointFilter(filter) {
1569
1857
  if (!filter) {
1570
1858
  return null;
@@ -1578,7 +1866,18 @@ var WorldOrbit = (() => {
1578
1866
  return normalized.query || normalized.objectTypes.length > 0 || normalized.tags.length > 0 || normalized.groupIds.length > 0 ? normalized : null;
1579
1867
  }
1580
1868
  function parseViewProjection(value) {
1581
- return value.toLowerCase() === "isometric" ? "isometric" : value.toLowerCase() === "topdown" ? "topdown" : null;
1869
+ switch (value.toLowerCase()) {
1870
+ case "topdown":
1871
+ return "topdown";
1872
+ case "isometric":
1873
+ return "isometric";
1874
+ case "orthographic":
1875
+ return "orthographic";
1876
+ case "perspective":
1877
+ return "perspective";
1878
+ default:
1879
+ return null;
1880
+ }
1582
1881
  }
1583
1882
  function parseRenderPreset(value) {
1584
1883
  const normalized = value.toLowerCase();
@@ -1605,7 +1904,7 @@ var WorldOrbit = (() => {
1605
1904
  next["orbits-front"] = enabled;
1606
1905
  continue;
1607
1906
  }
1608
- if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
1907
+ if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "events" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
1609
1908
  next[rawLayer] = enabled;
1610
1909
  }
1611
1910
  }
@@ -1616,7 +1915,7 @@ var WorldOrbit = (() => {
1616
1915
  }
1617
1916
  function parseViewpointGroups(value, document2, relationships, objectMap) {
1618
1917
  return splitListValue(value).map((entry) => {
1619
- if (document2.schemaVersion === "2.1" || document2.groups.some((group) => group.id === entry)) {
1918
+ if (document2.schemaVersion === "2.1" || document2.schemaVersion === "2.5" || document2.groups.some((group) => group.id === entry)) {
1620
1919
  return entry;
1621
1920
  }
1622
1921
  if (entry.startsWith("wo-") && entry.endsWith("-group")) {
@@ -1653,7 +1952,7 @@ var WorldOrbit = (() => {
1653
1952
  }
1654
1953
  return parts.join(" - ");
1655
1954
  }
1656
- function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels) {
1955
+ function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, labelMultiplier) {
1657
1956
  let minX = Number.POSITIVE_INFINITY;
1658
1957
  let minY = Number.POSITIVE_INFINITY;
1659
1958
  let maxX = Number.NEGATIVE_INFINITY;
@@ -1683,7 +1982,7 @@ var WorldOrbit = (() => {
1683
1982
  for (const label of labels) {
1684
1983
  if (label.hidden)
1685
1984
  continue;
1686
- includeLabelBounds(label, include);
1985
+ includeLabelBounds(label, include, labelMultiplier);
1687
1986
  }
1688
1987
  if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
1689
1988
  return createBounds(0, 0, width, height);
@@ -1721,13 +2020,10 @@ var WorldOrbit = (() => {
1721
2020
  include(object.x - object.visualRadius - 24, object.y - object.visualRadius - 16);
1722
2021
  include(object.x + object.visualRadius + 24, object.y + object.visualRadius + 36);
1723
2022
  }
1724
- function includeLabelBounds(label, include) {
1725
- const labelScale = 1;
1726
- const labelHalfWidth = estimateLabelHalfWidthFromText(label.label, label.secondaryLabel, labelScale);
1727
- include(label.x - labelHalfWidth, label.y - 18);
1728
- include(label.x + labelHalfWidth, label.y + 8);
1729
- include(label.x - labelHalfWidth, label.secondaryY - 14);
1730
- include(label.x + labelHalfWidth, label.secondaryY + 8);
2023
+ function includeLabelBounds(label, include, labelMultiplier) {
2024
+ const bounds = createLabelRectFromText(label.x, label.y, label.secondaryY, label.textAnchor, label.direction, label.label, label.secondaryLabel, labelMultiplier);
2025
+ include(bounds.left, bounds.top);
2026
+ include(bounds.right, bounds.bottom);
1731
2027
  }
1732
2028
  function placeObject(object, x, y, depth, positions, orbitDrafts, leaderDrafts, context) {
1733
2029
  if (positions.has(object.id)) {
@@ -2117,7 +2413,7 @@ var WorldOrbit = (() => {
2117
2413
  return null;
2118
2414
  }
2119
2415
  }
2120
- function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels) {
2416
+ function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier) {
2121
2417
  let minX = Number.POSITIVE_INFINITY;
2122
2418
  let minY = Number.POSITIVE_INFINITY;
2123
2419
  let maxX = Number.NEGATIVE_INFINITY;
@@ -2146,7 +2442,7 @@ var WorldOrbit = (() => {
2146
2442
  }
2147
2443
  for (const label of labels) {
2148
2444
  if (!label.hidden && group.labelIds.includes(label.objectId)) {
2149
- includeLabelBounds(label, include);
2445
+ includeLabelBounds(label, include, labelMultiplier);
2150
2446
  }
2151
2447
  }
2152
2448
  if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
@@ -2171,12 +2467,28 @@ var WorldOrbit = (() => {
2171
2467
  }
2172
2468
  return current.id;
2173
2469
  }
2174
- function createLabelRect(x, labelY, secondaryY, labelHalfWidth, direction) {
2470
+ function createLabelRect(object, placement, labelMultiplier) {
2471
+ return createLabelRectFromText(placement.x, placement.labelY, placement.secondaryY, placement.textAnchor, placement.direction, object.label, object.secondaryLabel, labelMultiplier);
2472
+ }
2473
+ function createLabelRectFromText(x, labelY, secondaryY, textAnchor, direction, label, secondaryLabel, labelMultiplier) {
2474
+ const labelHalfWidth = estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier);
2475
+ const labelWidth = labelHalfWidth * 2;
2476
+ const topPadding = direction === "above" ? 18 : 12;
2477
+ const bottomPadding = direction === "above" ? 8 : 12;
2478
+ let left = x - labelHalfWidth;
2479
+ let right = x + labelHalfWidth;
2480
+ if (textAnchor === "start") {
2481
+ left = x;
2482
+ right = x + labelWidth;
2483
+ } else if (textAnchor === "end") {
2484
+ left = x - labelWidth;
2485
+ right = x;
2486
+ }
2175
2487
  return {
2176
- left: x - labelHalfWidth,
2177
- right: x + labelHalfWidth,
2178
- top: Math.min(labelY, secondaryY) - (direction < 0 ? 18 : 12),
2179
- bottom: Math.max(labelY, secondaryY) + (direction < 0 ? 8 : 12)
2488
+ left,
2489
+ right,
2490
+ top: Math.min(labelY, secondaryY) - topPadding,
2491
+ bottom: Math.max(labelY, secondaryY) + bottomPadding
2180
2492
  };
2181
2493
  }
2182
2494
  function rectsOverlap(left, right) {
@@ -2363,11 +2675,6 @@ var WorldOrbit = (() => {
2363
2675
  function customColorFor(value) {
2364
2676
  return typeof value === "string" && value.trim() ? value : void 0;
2365
2677
  }
2366
- function estimateLabelHalfWidth(object, labelMultiplier) {
2367
- const primaryWidth = object.label.length * 4.6 * labelMultiplier + 18;
2368
- const secondaryWidth = object.secondaryLabel.length * 3.9 * labelMultiplier + 18;
2369
- return Math.max(primaryWidth, secondaryWidth, object.visualRadius + 18);
2370
- }
2371
2678
  function estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier) {
2372
2679
  const primaryWidth = label.length * 4.6 * labelMultiplier + 18;
2373
2680
  const secondaryWidth = secondaryLabel.length * 3.9 * labelMultiplier + 18;
@@ -2401,12 +2708,13 @@ var WorldOrbit = (() => {
2401
2708
  }
2402
2709
  return {
2403
2710
  format: "worldorbit",
2404
- version: "2.0",
2405
- schemaVersion: "2.0",
2711
+ version: "2.5",
2712
+ schemaVersion: "2.5",
2406
2713
  sourceVersion: document2.version,
2407
2714
  system,
2408
2715
  groups: structuredClone(document2.groups ?? []),
2409
2716
  relations: structuredClone(document2.relations ?? []),
2717
+ events: structuredClone(document2.events ?? []),
2410
2718
  objects: document2.objects.map(cloneWorldOrbitObject),
2411
2719
  diagnostics
2412
2720
  };
@@ -2414,7 +2722,7 @@ var WorldOrbit = (() => {
2414
2722
  function upgradeDocumentToDraftV2(document2, options = {}) {
2415
2723
  return convertAtlasDocumentToLegacyDraft(upgradeDocumentToV2(document2, options));
2416
2724
  }
2417
- function materializeAtlasDocument(document2) {
2725
+ function materializeAtlasDocument(document2, options = {}) {
2418
2726
  const system = document2.system ? {
2419
2727
  type: "system",
2420
2728
  id: document2.system.id,
@@ -2425,6 +2733,8 @@ var WorldOrbit = (() => {
2425
2733
  properties: materializeDraftSystemProperties(document2.system),
2426
2734
  info: materializeDraftSystemInfo(document2.system)
2427
2735
  } : null;
2736
+ const objects = document2.objects.map(cloneWorldOrbitObject);
2737
+ applyEventPoseOverrides(objects, document2.events ?? [], options.activeEventId ?? null);
2428
2738
  return {
2429
2739
  format: "worldorbit",
2430
2740
  version: "1.0",
@@ -2432,7 +2742,8 @@ var WorldOrbit = (() => {
2432
2742
  system,
2433
2743
  groups: structuredClone(document2.groups ?? []),
2434
2744
  relations: structuredClone(document2.relations ?? []),
2435
- objects: document2.objects.map(cloneWorldOrbitObject)
2745
+ events: document2.events.map(cloneWorldOrbitEvent),
2746
+ objects
2436
2747
  };
2437
2748
  }
2438
2749
  function createDraftSystem(document2, defaults, atlasMetadata, annotations, diagnostics, preset) {
@@ -2454,8 +2765,9 @@ var WorldOrbit = (() => {
2454
2765
  };
2455
2766
  }
2456
2767
  function createDraftDefaults(document2, preset, projection) {
2768
+ const rawView = typeof document2.system?.properties.view === "string" ? document2.system.properties.view.toLowerCase() : null;
2457
2769
  return {
2458
- view: typeof document2.system?.properties.view === "string" && document2.system.properties.view.toLowerCase() === "topdown" ? "topdown" : projection,
2770
+ view: rawView === "topdown" || rawView === "isometric" || rawView === "orthographic" || rawView === "perspective" ? rawView : projection,
2459
2771
  scale: typeof document2.system?.properties.scale === "string" ? document2.system.properties.scale : null,
2460
2772
  units: typeof document2.system?.properties.units === "string" ? document2.system.properties.units : null,
2461
2773
  preset,
@@ -2557,10 +2869,12 @@ var WorldOrbit = (() => {
2557
2869
  summary: viewpoint.summary,
2558
2870
  focusObjectId: viewpoint.objectId,
2559
2871
  selectedObjectId: viewpoint.selectedObjectId,
2872
+ events: [...viewpoint.eventIds],
2560
2873
  projection: viewpoint.projection,
2561
2874
  preset: viewpoint.preset,
2562
2875
  zoom: viewpoint.scale,
2563
2876
  rotationDeg: viewpoint.rotationDeg,
2877
+ camera: viewpoint.camera ? { ...viewpoint.camera } : null,
2564
2878
  layers: { ...viewpoint.layers },
2565
2879
  filter: viewpoint.filter ? {
2566
2880
  query: viewpoint.filter.query,
@@ -2589,6 +2903,75 @@ var WorldOrbit = (() => {
2589
2903
  info: { ...object.info }
2590
2904
  };
2591
2905
  }
2906
+ function cloneWorldOrbitEvent(event) {
2907
+ return {
2908
+ ...event,
2909
+ participantObjectIds: [...event.participantObjectIds],
2910
+ tags: [...event.tags],
2911
+ positions: event.positions.map(cloneWorldOrbitEventPose)
2912
+ };
2913
+ }
2914
+ function cloneWorldOrbitEventPose(pose) {
2915
+ return {
2916
+ objectId: pose.objectId,
2917
+ placement: clonePlacement(pose.placement),
2918
+ inner: pose.inner ? { ...pose.inner } : void 0,
2919
+ outer: pose.outer ? { ...pose.outer } : void 0,
2920
+ epoch: pose.epoch ?? null,
2921
+ referencePlane: pose.referencePlane ?? null
2922
+ };
2923
+ }
2924
+ function clonePlacement(placement) {
2925
+ return placement ? structuredClone(placement) : null;
2926
+ }
2927
+ function applyEventPoseOverrides(objects, events, activeEventId) {
2928
+ if (!activeEventId) {
2929
+ return;
2930
+ }
2931
+ const event = events.find((entry) => entry.id === activeEventId);
2932
+ if (!event) {
2933
+ return;
2934
+ }
2935
+ const objectMap = new Map(objects.map((object) => [object.id, object]));
2936
+ const referencedIds = /* @__PURE__ */ new Set([
2937
+ ...event.targetObjectId ? [event.targetObjectId] : [],
2938
+ ...event.participantObjectIds,
2939
+ ...event.positions.map((pose) => pose.objectId)
2940
+ ]);
2941
+ for (const objectId of referencedIds) {
2942
+ const object = objectMap.get(objectId);
2943
+ if (!object) {
2944
+ continue;
2945
+ }
2946
+ if (event.epoch) {
2947
+ object.epoch = event.epoch;
2948
+ }
2949
+ if (event.referencePlane) {
2950
+ object.referencePlane = event.referencePlane;
2951
+ }
2952
+ }
2953
+ for (const pose of event.positions) {
2954
+ const object = objectMap.get(pose.objectId);
2955
+ if (!object) {
2956
+ continue;
2957
+ }
2958
+ if (pose.placement) {
2959
+ object.placement = clonePlacement(pose.placement);
2960
+ }
2961
+ if (pose.inner) {
2962
+ object.properties.inner = { ...pose.inner };
2963
+ }
2964
+ if (pose.outer) {
2965
+ object.properties.outer = { ...pose.outer };
2966
+ }
2967
+ if (pose.epoch) {
2968
+ object.epoch = pose.epoch;
2969
+ }
2970
+ if (pose.referencePlane) {
2971
+ object.referencePlane = pose.referencePlane;
2972
+ }
2973
+ }
2974
+ }
2592
2975
  function cloneProperties(properties) {
2593
2976
  const next = {};
2594
2977
  for (const [key, value] of Object.entries(properties)) {
@@ -2670,6 +3053,18 @@ var WorldOrbit = (() => {
2670
3053
  if (viewpoint.rotationDeg !== 0) {
2671
3054
  info2[`${prefix}.rotation`] = String(viewpoint.rotationDeg);
2672
3055
  }
3056
+ if (viewpoint.camera?.azimuth !== null) {
3057
+ info2[`${prefix}.camera.azimuth`] = String(viewpoint.camera?.azimuth);
3058
+ }
3059
+ if (viewpoint.camera?.elevation !== null) {
3060
+ info2[`${prefix}.camera.elevation`] = String(viewpoint.camera?.elevation);
3061
+ }
3062
+ if (viewpoint.camera?.roll !== null) {
3063
+ info2[`${prefix}.camera.roll`] = String(viewpoint.camera?.roll);
3064
+ }
3065
+ if (viewpoint.camera?.distance !== null) {
3066
+ info2[`${prefix}.camera.distance`] = String(viewpoint.camera?.distance);
3067
+ }
2673
3068
  const serializedLayers = serializeViewpointLayers(viewpoint.layers);
2674
3069
  if (serializedLayers) {
2675
3070
  info2[`${prefix}.layers`] = serializedLayers;
@@ -2686,6 +3081,9 @@ var WorldOrbit = (() => {
2686
3081
  if ((viewpoint.filter?.groupIds.length ?? 0) > 0) {
2687
3082
  info2[`${prefix}.groups`] = viewpoint.filter?.groupIds.join(" ") ?? "";
2688
3083
  }
3084
+ if (viewpoint.events.length > 0) {
3085
+ info2[`${prefix}.events`] = viewpoint.events.join(" ");
3086
+ }
2689
3087
  }
2690
3088
  for (const annotation of system.annotations) {
2691
3089
  const prefix = `annotation.${annotation.id}`;
@@ -2710,7 +3108,7 @@ var WorldOrbit = (() => {
2710
3108
  if (orbitFront !== void 0 || orbitBack !== void 0) {
2711
3109
  tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
2712
3110
  }
2713
- for (const key of ["background", "guides", "relations", "objects", "labels", "metadata"]) {
3111
+ for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
2714
3112
  if (layers[key] !== void 0) {
2715
3113
  tokens.push(layers[key] ? key : `-${key}`);
2716
3114
  }
@@ -2763,26 +3161,26 @@ var WorldOrbit = (() => {
2763
3161
  ];
2764
3162
  function formatDocument(document2, options = {}) {
2765
3163
  const schema = options.schema ?? "auto";
2766
- const useDraft = schema === "2.0" || schema === "2.1" || schema === "2.0-draft" || document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.0-draft";
3164
+ const useDraft = schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.0-draft" || document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" || document2.version === "2.0-draft";
2767
3165
  if (useDraft) {
2768
3166
  if (schema === "2.0-draft") {
2769
- const legacyDraftDocument = document2.version === "2.0-draft" ? document2 : document2.version === "2.0" || document2.version === "2.1" ? {
3167
+ const legacyDraftDocument = document2.version === "2.0-draft" ? document2 : document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" ? {
2770
3168
  ...document2,
2771
3169
  version: "2.0-draft",
2772
3170
  schemaVersion: "2.0-draft"
2773
3171
  } : upgradeDocumentToDraftV2(document2);
2774
3172
  return formatDraftDocument(legacyDraftDocument);
2775
3173
  }
2776
- const atlasDocument = document2.version === "2.0" || document2.version === "2.1" ? document2 : document2.version === "2.0-draft" ? {
3174
+ const atlasDocument = document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" ? document2 : document2.version === "2.0-draft" ? {
2777
3175
  ...document2,
2778
3176
  version: "2.0",
2779
3177
  schemaVersion: "2.0"
2780
3178
  } : upgradeDocumentToV2(document2);
2781
- if (schema === "2.1" && atlasDocument.version !== "2.1") {
3179
+ if ((schema === "2.0" || schema === "2.1" || schema === "2.5") && atlasDocument.version !== schema) {
2782
3180
  return formatAtlasDocument({
2783
3181
  ...atlasDocument,
2784
- version: "2.1",
2785
- schemaVersion: "2.1"
3182
+ version: schema,
3183
+ schemaVersion: schema
2786
3184
  });
2787
3185
  }
2788
3186
  return formatAtlasDocument(atlasDocument);
@@ -2814,6 +3212,10 @@ var WorldOrbit = (() => {
2814
3212
  lines.push("");
2815
3213
  lines.push(...formatAtlasRelation(relation));
2816
3214
  }
3215
+ for (const event of [...document2.events].sort(compareIdLike)) {
3216
+ lines.push("");
3217
+ lines.push(...formatAtlasEvent(event));
3218
+ }
2817
3219
  const sortedObjects = [...document2.objects].sort(compareObjects);
2818
3220
  if (sortedObjects.length > 0 && lines.at(-1) !== "") {
2819
3221
  lines.push("");
@@ -2844,6 +3246,10 @@ var WorldOrbit = (() => {
2844
3246
  lines.push("");
2845
3247
  lines.push(...formatAtlasRelation(relation));
2846
3248
  }
3249
+ for (const event of [...legacy.events].sort(compareIdLike)) {
3250
+ lines.push("");
3251
+ lines.push(...formatAtlasEvent(event));
3252
+ }
2847
3253
  const sortedObjects = [...legacy.objects].sort(compareObjects);
2848
3254
  if (sortedObjects.length > 0 && lines.at(-1) !== "") {
2849
3255
  lines.push("");
@@ -3051,10 +3457,28 @@ var WorldOrbit = (() => {
3051
3457
  if (viewpoint.rotationDeg !== 0) {
3052
3458
  lines.push(` rotation ${viewpoint.rotationDeg}`);
3053
3459
  }
3460
+ if (viewpoint.camera && hasCameraValues(viewpoint.camera)) {
3461
+ lines.push(" camera");
3462
+ if (viewpoint.camera.azimuth !== null) {
3463
+ lines.push(` azimuth ${viewpoint.camera.azimuth}`);
3464
+ }
3465
+ if (viewpoint.camera.elevation !== null) {
3466
+ lines.push(` elevation ${viewpoint.camera.elevation}`);
3467
+ }
3468
+ if (viewpoint.camera.roll !== null) {
3469
+ lines.push(` roll ${viewpoint.camera.roll}`);
3470
+ }
3471
+ if (viewpoint.camera.distance !== null) {
3472
+ lines.push(` distance ${viewpoint.camera.distance}`);
3473
+ }
3474
+ }
3054
3475
  const layerTokens = formatDraftLayers(viewpoint.layers);
3055
3476
  if (layerTokens.length > 0) {
3056
3477
  lines.push(` layers ${layerTokens.join(" ")}`);
3057
3478
  }
3479
+ if (viewpoint.events.length > 0) {
3480
+ lines.push(` events ${viewpoint.events.join(" ")}`);
3481
+ }
3058
3482
  if (viewpoint.filter) {
3059
3483
  lines.push(" filter");
3060
3484
  if (viewpoint.filter.query) {
@@ -3127,6 +3551,65 @@ var WorldOrbit = (() => {
3127
3551
  }
3128
3552
  return lines;
3129
3553
  }
3554
+ function formatAtlasEvent(event) {
3555
+ const lines = [`event ${event.id}`, ` kind ${quoteIfNeeded(event.kind)}`];
3556
+ if (event.label) {
3557
+ lines.push(` label ${quoteIfNeeded(event.label)}`);
3558
+ }
3559
+ if (event.summary) {
3560
+ lines.push(` summary ${quoteIfNeeded(event.summary)}`);
3561
+ }
3562
+ if (event.targetObjectId) {
3563
+ lines.push(` target ${event.targetObjectId}`);
3564
+ }
3565
+ if (event.participantObjectIds.length > 0) {
3566
+ lines.push(` participants ${event.participantObjectIds.join(" ")}`);
3567
+ }
3568
+ if (event.timing) {
3569
+ lines.push(` timing ${quoteIfNeeded(event.timing)}`);
3570
+ }
3571
+ if (event.visibility) {
3572
+ lines.push(` visibility ${quoteIfNeeded(event.visibility)}`);
3573
+ }
3574
+ if (event.epoch) {
3575
+ lines.push(` epoch ${quoteIfNeeded(event.epoch)}`);
3576
+ }
3577
+ if (event.referencePlane) {
3578
+ lines.push(` referencePlane ${quoteIfNeeded(event.referencePlane)}`);
3579
+ }
3580
+ if (event.tags.length > 0) {
3581
+ lines.push(` tags ${event.tags.map(quoteIfNeeded).join(" ")}`);
3582
+ }
3583
+ if (event.color) {
3584
+ lines.push(` color ${quoteIfNeeded(event.color)}`);
3585
+ }
3586
+ if (event.hidden) {
3587
+ lines.push(" hidden true");
3588
+ }
3589
+ if (event.positions.length > 0) {
3590
+ lines.push("");
3591
+ lines.push(" positions");
3592
+ for (const pose of [...event.positions].sort(comparePoseObjectId)) {
3593
+ lines.push(` pose ${pose.objectId}`);
3594
+ for (const fieldLine of formatEventPoseFields(pose)) {
3595
+ lines.push(` ${fieldLine}`);
3596
+ }
3597
+ }
3598
+ }
3599
+ return lines;
3600
+ }
3601
+ function formatEventPoseFields(pose) {
3602
+ return [
3603
+ ...formatPlacement(pose.placement),
3604
+ ...pose.epoch ? [`epoch ${quoteIfNeeded(pose.epoch)}`] : [],
3605
+ ...pose.referencePlane ? [`referencePlane ${quoteIfNeeded(pose.referencePlane)}`] : [],
3606
+ ...formatOptionalUnit("inner", pose.inner),
3607
+ ...formatOptionalUnit("outer", pose.outer)
3608
+ ];
3609
+ }
3610
+ function hasCameraValues(camera) {
3611
+ return camera.azimuth !== null || camera.elevation !== null || camera.roll !== null || camera.distance !== null;
3612
+ }
3130
3613
  function formatValue(value) {
3131
3614
  if (Array.isArray(value)) {
3132
3615
  return value.map((item) => quoteIfNeeded(item)).join(" ");
@@ -3168,7 +3651,7 @@ var WorldOrbit = (() => {
3168
3651
  if (orbitFront !== void 0 || orbitBack !== void 0) {
3169
3652
  tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
3170
3653
  }
3171
- for (const key of ["background", "guides", "relations", "objects", "labels", "metadata"]) {
3654
+ for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
3172
3655
  if (layers[key] !== void 0) {
3173
3656
  tokens.push(layers[key] ? key : `-${key}`);
3174
3657
  }
@@ -3196,6 +3679,9 @@ var WorldOrbit = (() => {
3196
3679
  function compareIdLike(left, right) {
3197
3680
  return left.id.localeCompare(right.id);
3198
3681
  }
3682
+ function comparePoseObjectId(left, right) {
3683
+ return left.objectId.localeCompare(right.objectId);
3684
+ }
3199
3685
  function objectTypeIndex(objectType) {
3200
3686
  switch (objectType) {
3201
3687
  case "star":
@@ -3391,6 +3877,7 @@ var WorldOrbit = (() => {
3391
3877
  const diagnostics = [];
3392
3878
  const objectMap = new Map(document2.objects.map((object) => [object.id, object]));
3393
3879
  const groupIds = new Set(document2.groups.map((group) => group.id));
3880
+ const eventIds = new Set(document2.events.map((event) => event.id));
3394
3881
  if (!document2.system) {
3395
3882
  diagnostics.push(error("validate.system.required", "Atlas documents must declare exactly one system."));
3396
3883
  }
@@ -3400,6 +3887,7 @@ var WorldOrbit = (() => {
3400
3887
  ["viewpoint", document2.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []],
3401
3888
  ["annotation", document2.system?.annotations.map((annotation) => annotation.id) ?? []],
3402
3889
  ["relation", document2.relations.map((relation) => relation.id)],
3890
+ ["event", document2.events.map((event) => event.id)],
3403
3891
  ["object", document2.objects.map((object) => object.id)]
3404
3892
  ]) {
3405
3893
  for (const id of ids) {
@@ -3415,11 +3903,14 @@ var WorldOrbit = (() => {
3415
3903
  validateRelation(relation, objectMap, diagnostics);
3416
3904
  }
3417
3905
  for (const viewpoint of document2.system?.viewpoints ?? []) {
3418
- validateViewpointFilter(viewpoint.filter, groupIds, sourceSchemaVersion, diagnostics, viewpoint.id);
3906
+ validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap);
3419
3907
  }
3420
3908
  for (const object of document2.objects) {
3421
3909
  validateObject(object, document2.system, objectMap, groupIds, diagnostics);
3422
3910
  }
3911
+ for (const event of document2.events) {
3912
+ validateEvent(event, document2.system, objectMap, diagnostics);
3913
+ }
3423
3914
  return diagnostics;
3424
3915
  }
3425
3916
  function validateRelation(relation, objectMap, diagnostics) {
@@ -3437,15 +3928,24 @@ var WorldOrbit = (() => {
3437
3928
  diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
3438
3929
  }
3439
3930
  }
3440
- function validateViewpointFilter(filter, groupIds, sourceSchemaVersion, diagnostics, viewpointId) {
3441
- if (!filter || sourceSchemaVersion !== "2.1") {
3442
- return;
3443
- }
3444
- for (const groupId of filter.groupIds) {
3445
- if (!groupIds.has(groupId)) {
3446
- diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpointId}".`));
3931
+ function validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap) {
3932
+ const filter = viewpoint.filter;
3933
+ if (sourceSchemaVersion === "2.1" || sourceSchemaVersion === "2.5") {
3934
+ if (filter) {
3935
+ for (const groupId of filter.groupIds) {
3936
+ if (!groupIds.has(groupId)) {
3937
+ diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.groups`));
3938
+ }
3939
+ }
3940
+ }
3941
+ for (const eventId of viewpoint.events ?? []) {
3942
+ if (!eventIds.has(eventId)) {
3943
+ diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.events`));
3944
+ }
3447
3945
  }
3448
3946
  }
3947
+ validateProjection(viewpoint.projection, diagnostics, `viewpoint.${viewpoint.id}.projection`, viewpoint.id);
3948
+ validateCamera(viewpoint.camera, viewpoint.projection, viewpoint.rotationDeg, diagnostics, viewpoint.id, viewpoint.focusObjectId, viewpoint.selectedObjectId, filter, objectMap);
3449
3949
  }
3450
3950
  function validateObject(object, system, objectMap, groupIds, diagnostics) {
3451
3951
  const placement = object.placement;
@@ -3458,6 +3958,12 @@ var WorldOrbit = (() => {
3458
3958
  }
3459
3959
  }
3460
3960
  }
3961
+ if (typeof object.epoch === "string" && !object.epoch.trim()) {
3962
+ diagnostics.push(warn("validate.epoch.empty", `Object "${object.id}" defines an empty epoch string.`, object.id, "epoch"));
3963
+ }
3964
+ if (typeof object.referencePlane === "string" && !object.referencePlane.trim()) {
3965
+ diagnostics.push(warn("validate.referencePlane.empty", `Object "${object.id}" defines an empty reference plane string.`, object.id, "referencePlane"));
3966
+ }
3461
3967
  if (orbitPlacement) {
3462
3968
  if (!objectMap.has(orbitPlacement.target)) {
3463
3969
  diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
@@ -3529,6 +4035,122 @@ var WorldOrbit = (() => {
3529
4035
  }
3530
4036
  }
3531
4037
  }
4038
+ function validateEvent(event, system, objectMap, diagnostics) {
4039
+ const fieldPrefix = `event.${event.id}`;
4040
+ const referencedIds = /* @__PURE__ */ new Set();
4041
+ if (!event.kind.trim()) {
4042
+ diagnostics.push(error("validate.event.kind.required", `Event "${event.id}" is missing a "kind" value.`, void 0, `${fieldPrefix}.kind`));
4043
+ }
4044
+ if (typeof event.epoch === "string" && !event.epoch.trim()) {
4045
+ diagnostics.push(warn("validate.event.epoch.empty", `Event "${event.id}" defines an empty epoch string.`, void 0, `${fieldPrefix}.epoch`));
4046
+ }
4047
+ if (typeof event.referencePlane === "string" && !event.referencePlane.trim()) {
4048
+ diagnostics.push(warn("validate.event.referencePlane.empty", `Event "${event.id}" defines an empty reference plane string.`, void 0, `${fieldPrefix}.referencePlane`));
4049
+ }
4050
+ if (!event.targetObjectId && event.participantObjectIds.length === 0) {
4051
+ diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
4052
+ }
4053
+ if (event.targetObjectId) {
4054
+ referencedIds.add(event.targetObjectId);
4055
+ if (!objectMap.has(event.targetObjectId)) {
4056
+ diagnostics.push(error("validate.event.target.unknown", `Unknown event target "${event.targetObjectId}" on "${event.id}".`, void 0, `${fieldPrefix}.target`));
4057
+ }
4058
+ }
4059
+ const seenParticipants = /* @__PURE__ */ new Set();
4060
+ for (const participantId of event.participantObjectIds) {
4061
+ referencedIds.add(participantId);
4062
+ if (seenParticipants.has(participantId)) {
4063
+ diagnostics.push(warn("validate.event.participants.duplicate", `Event "${event.id}" repeats participant "${participantId}".`, void 0, `${fieldPrefix}.participants`));
4064
+ continue;
4065
+ }
4066
+ seenParticipants.add(participantId);
4067
+ if (!objectMap.has(participantId)) {
4068
+ diagnostics.push(error("validate.event.participants.unknown", `Unknown event participant "${participantId}" on "${event.id}".`, void 0, `${fieldPrefix}.participants`));
4069
+ }
4070
+ }
4071
+ if (event.targetObjectId && event.participantObjectIds.length > 0 && !event.participantObjectIds.includes(event.targetObjectId)) {
4072
+ diagnostics.push(warn("validate.event.target.notParticipant", `Event "${event.id}" defines a target outside its participants list.`, void 0, `${fieldPrefix}.target`));
4073
+ }
4074
+ if (event.positions.length === 0) {
4075
+ diagnostics.push(warn("validate.event.positions.missing", `Event "${event.id}" has no positions block and cannot drive a scene snapshot.`, void 0, `${fieldPrefix}.positions`));
4076
+ }
4077
+ if (/(?:^|[-_])(solar-eclipse|lunar-eclipse|transit|occultation)(?:$|[-_])/.test(event.kind) && referencedIds.size < 3) {
4078
+ 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`));
4079
+ }
4080
+ const poseIds = /* @__PURE__ */ new Set();
4081
+ for (const pose of event.positions) {
4082
+ const poseFieldPrefix = `${fieldPrefix}.pose.${pose.objectId}`;
4083
+ if (poseIds.has(pose.objectId)) {
4084
+ diagnostics.push(error("validate.event.pose.duplicate", `Event "${event.id}" defines "${pose.objectId}" more than once in positions.`, void 0, poseFieldPrefix));
4085
+ continue;
4086
+ }
4087
+ poseIds.add(pose.objectId);
4088
+ const object = objectMap.get(pose.objectId);
4089
+ if (!object) {
4090
+ diagnostics.push(error("validate.event.pose.object.unknown", `Unknown event pose object "${pose.objectId}" on "${event.id}".`, void 0, poseFieldPrefix));
4091
+ continue;
4092
+ }
4093
+ if (!referencedIds.has(pose.objectId)) {
4094
+ diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
4095
+ }
4096
+ validateEventPose(pose, object, event, system, objectMap, diagnostics, poseFieldPrefix, event.id);
4097
+ }
4098
+ const missingPoseIds = [...referencedIds].filter((objectId) => !poseIds.has(objectId));
4099
+ if (event.positions.length > 0 && missingPoseIds.length > 0) {
4100
+ diagnostics.push(warn("validate.event.positions.partial", `Event "${event.id}" leaves ${missingPoseIds.length} referenced object(s) on their base placement.`, void 0, `${fieldPrefix}.positions`));
4101
+ }
4102
+ }
4103
+ function validateEventPose(pose, object, event, system, objectMap, diagnostics, fieldPrefix, eventId) {
4104
+ const placement = pose.placement;
4105
+ if (!placement) {
4106
+ diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
4107
+ return;
4108
+ }
4109
+ if (placement.mode === "orbit") {
4110
+ if (!objectMap.has(placement.target)) {
4111
+ diagnostics.push(error("validate.event.pose.orbit.target.unknown", `Unknown event orbit target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.orbit`));
4112
+ }
4113
+ if (placement.distance && placement.semiMajor) {
4114
+ diagnostics.push(error("validate.event.pose.orbit.distanceConflict", `Event "${eventId}" pose "${pose.objectId}" cannot declare both "distance" and "semiMajor".`, void 0, `${fieldPrefix}.distance`));
4115
+ }
4116
+ if (placement.phase && !resolveEffectiveEpoch(system, object, event, pose)) {
4117
+ diagnostics.push(warn("validate.event.pose.phase.epochMissing", `Event "${eventId}" pose "${pose.objectId}" sets "phase" without an effective epoch.`, void 0, `${fieldPrefix}.phase`));
4118
+ }
4119
+ if (placement.inclination && !resolveEffectiveReferencePlane(system, object, event, pose)) {
4120
+ diagnostics.push(warn("validate.event.pose.inclination.referencePlaneMissing", `Event "${eventId}" pose "${pose.objectId}" sets "inclination" without an effective reference plane.`, void 0, `${fieldPrefix}.inclination`));
4121
+ }
4122
+ if (placement.period && !massInSolar(objectMap.get(placement.target)?.properties.mass)) {
4123
+ diagnostics.push(warn("validate.event.pose.period.massMissing", `Event "${eventId}" pose "${pose.objectId}" sets "period" but its central mass cannot be derived.`, void 0, `${fieldPrefix}.period`));
4124
+ }
4125
+ return;
4126
+ }
4127
+ if (placement.mode === "surface") {
4128
+ const target = objectMap.get(placement.target);
4129
+ if (!target) {
4130
+ diagnostics.push(error("validate.event.pose.surface.target.unknown", `Unknown event surface target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.surface`));
4131
+ } else if (!SURFACE_TARGET_TYPES2.has(target.type)) {
4132
+ 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`));
4133
+ }
4134
+ return;
4135
+ }
4136
+ if (placement.mode === "at") {
4137
+ if (object.type !== "structure" && object.type !== "phenomenon") {
4138
+ 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`));
4139
+ }
4140
+ const reference = placement.reference;
4141
+ if (reference.kind === "named" && !objectMap.has(reference.name)) {
4142
+ diagnostics.push(error("validate.event.pose.at.target.unknown", `Unknown event at-reference target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
4143
+ } else if (reference.kind === "anchor" && !objectMap.has(reference.objectId)) {
4144
+ diagnostics.push(error("validate.event.pose.anchor.target.unknown", `Unknown event anchor target "${reference.objectId}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
4145
+ } else if (reference.kind === "lagrange") {
4146
+ if (!objectMap.has(reference.primary)) {
4147
+ diagnostics.push(error("validate.event.pose.lagrange.primary.unknown", `Unknown event Lagrange target "${reference.primary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
4148
+ } else if (reference.secondary && !objectMap.has(reference.secondary)) {
4149
+ diagnostics.push(error("validate.event.pose.lagrange.secondary.unknown", `Unknown event Lagrange target "${reference.secondary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
4150
+ }
4151
+ }
4152
+ }
4153
+ }
3532
4154
  function validateAtTarget(object, objectMap, diagnostics) {
3533
4155
  const reference = object.placement?.mode === "at" ? object.placement.reference : null;
3534
4156
  if (!reference) {
@@ -3634,6 +4256,52 @@ var WorldOrbit = (() => {
3634
4256
  return null;
3635
4257
  }
3636
4258
  }
4259
+ function validateProjection(projection, diagnostics, field, viewpointId) {
4260
+ if (projection !== "topdown" && projection !== "isometric" && projection !== "orthographic" && projection !== "perspective") {
4261
+ diagnostics.push(error("validate.viewpoint.projection.invalid", `Unknown projection "${String(projection)}" in viewpoint "${viewpointId}".`, void 0, field));
4262
+ }
4263
+ }
4264
+ function validateCamera(camera, projection, rotationDeg, diagnostics, viewpointId, focusObjectId, selectedObjectId, filter, objectMap) {
4265
+ if (!camera) {
4266
+ return;
4267
+ }
4268
+ const prefix = `viewpoint.${viewpointId}.camera`;
4269
+ for (const [key, value] of [
4270
+ ["azimuth", camera.azimuth],
4271
+ ["elevation", camera.elevation],
4272
+ ["roll", camera.roll],
4273
+ ["distance", camera.distance]
4274
+ ]) {
4275
+ if (value !== null && (!Number.isFinite(value) || key === "distance" && value <= 0)) {
4276
+ diagnostics.push(error("validate.viewpoint.camera.invalid", `Invalid camera ${key} "${String(value)}" in viewpoint "${viewpointId}".`, void 0, `${prefix}.${key}`));
4277
+ }
4278
+ }
4279
+ if (camera.distance !== null && projection !== "perspective") {
4280
+ diagnostics.push(warn("validate.viewpoint.camera.distance.partialEffect", `Camera "distance" only has a semantic effect in perspective viewpoints; "${viewpointId}" uses "${projection}".`, void 0, `${prefix}.distance`));
4281
+ }
4282
+ if (projection === "topdown" && (camera.elevation !== null || camera.roll !== null)) {
4283
+ diagnostics.push(warn("validate.viewpoint.camera.topdownPartial", `Camera elevation/roll on topdown viewpoint "${viewpointId}" are currently stored for future 3D use and only partially affect 2D rendering.`, void 0, prefix));
4284
+ }
4285
+ if (projection === "isometric" && camera.elevation !== null) {
4286
+ diagnostics.push(info("validate.viewpoint.camera.isometricStored", `Camera elevation on isometric viewpoint "${viewpointId}" is preserved semantically for future 3D rendering.`, void 0, `${prefix}.elevation`));
4287
+ }
4288
+ if (camera.azimuth !== null && camera.azimuth !== 0 && rotationDeg !== 0) {
4289
+ diagnostics.push(warn("validate.viewpoint.rotation.cameraOverlap", `Viewpoint "${viewpointId}" uses camera.azimuth; keep "rotation" only for 2D screen rotation to avoid ambiguity.`, void 0, `${prefix}.azimuth`));
4290
+ }
4291
+ const hasAnchor = focusObjectId !== null && objectMap.has(focusObjectId) || selectedObjectId !== null && objectMap.has(selectedObjectId) || !!filter;
4292
+ if (!hasAnchor) {
4293
+ diagnostics.push(info("validate.viewpoint.camera.anchorMissing", `Viewpoint "${viewpointId}" stores camera settings without a focus object, selection, or filter anchor.`, void 0, prefix));
4294
+ }
4295
+ }
4296
+ function resolveEffectiveEpoch(system, object, event, pose) {
4297
+ return normalizeOptionalContextString(pose?.epoch) ?? normalizeOptionalContextString(event?.epoch) ?? normalizeOptionalContextString(object.epoch) ?? normalizeOptionalContextString(system?.epoch) ?? null;
4298
+ }
4299
+ function resolveEffectiveReferencePlane(system, object, event, pose) {
4300
+ return normalizeOptionalContextString(pose?.referencePlane) ?? normalizeOptionalContextString(event?.referencePlane) ?? normalizeOptionalContextString(object.referencePlane) ?? normalizeOptionalContextString(system?.referencePlane) ?? null;
4301
+ }
4302
+ function normalizeOptionalContextString(value) {
4303
+ return typeof value === "string" && value.trim() ? value.trim() : null;
4304
+ }
3637
4305
  function toleranceForField(object, field) {
3638
4306
  const tolerance = object.tolerances?.find((entry) => entry.field === field)?.value;
3639
4307
  if (typeof tolerance === "number") {
@@ -3729,6 +4397,23 @@ var WorldOrbit = (() => {
3729
4397
  });
3730
4398
  }
3731
4399
  var DRAFT_OBJECT_FIELD_KEYS = new Set(DRAFT_OBJECT_FIELD_SPECS.keys());
4400
+ var EVENT_POSE_FIELD_KEYS = /* @__PURE__ */ new Set([
4401
+ "orbit",
4402
+ "distance",
4403
+ "semiMajor",
4404
+ "eccentricity",
4405
+ "period",
4406
+ "angle",
4407
+ "inclination",
4408
+ "phase",
4409
+ "at",
4410
+ "surface",
4411
+ "free",
4412
+ "inner",
4413
+ "outer",
4414
+ "epoch",
4415
+ "referencePlane"
4416
+ ]);
3732
4417
  function parseWorldOrbitAtlas(source) {
3733
4418
  return parseAtlasSource(source);
3734
4419
  }
@@ -3743,12 +4428,15 @@ var WorldOrbit = (() => {
3743
4428
  const objectNodes = [];
3744
4429
  const groups = [];
3745
4430
  const relations = [];
4431
+ const events = [];
4432
+ const eventPoseNodes = /* @__PURE__ */ new Map();
3746
4433
  let sawDefaults = false;
3747
4434
  let sawAtlas = false;
3748
4435
  const viewpointIds = /* @__PURE__ */ new Set();
3749
4436
  const annotationIds = /* @__PURE__ */ new Set();
3750
4437
  const groupIds = /* @__PURE__ */ new Set();
3751
4438
  const relationIds = /* @__PURE__ */ new Set();
4439
+ const eventIds = /* @__PURE__ */ new Set();
3752
4440
  for (let index = 0; index < lines.length; index++) {
3753
4441
  const rawLine = lines[index];
3754
4442
  const lineNumber = index + 1;
@@ -3766,7 +4454,7 @@ var WorldOrbit = (() => {
3766
4454
  if (!sawSchemaHeader) {
3767
4455
  sourceSchemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
3768
4456
  sawSchemaHeader = true;
3769
- if (prepared.comments.length > 0 && sourceSchemaVersion !== "2.1") {
4457
+ if (prepared.comments.length > 0 && isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
3770
4458
  diagnostics.push({
3771
4459
  code: "parse.schema21.commentCompatibility",
3772
4460
  severity: "warning",
@@ -3779,7 +4467,7 @@ var WorldOrbit = (() => {
3779
4467
  continue;
3780
4468
  }
3781
4469
  if (indent === 0) {
3782
- section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, { sawDefaults, sawAtlas });
4470
+ section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, { sawDefaults, sawAtlas });
3783
4471
  if (section.kind === "system") {
3784
4472
  system = section.system;
3785
4473
  } else if (section.kind === "defaults") {
@@ -3798,6 +4486,7 @@ var WorldOrbit = (() => {
3798
4486
  throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
3799
4487
  }
3800
4488
  const objects = objectNodes.map((node) => normalizeDraftObject(node, sourceSchemaVersion, diagnostics));
4489
+ const normalizedEvents = events.map((event) => normalizeDraftEvent(event, eventPoseNodes.get(event.id) ?? []));
3801
4490
  const outputVersion = forcedOutputVersion ?? (sourceSchemaVersion === "2.0-draft" ? "2.0" : sourceSchemaVersion);
3802
4491
  const baseDocument = {
3803
4492
  format: "worldorbit",
@@ -3805,6 +4494,7 @@ var WorldOrbit = (() => {
3805
4494
  system,
3806
4495
  groups,
3807
4496
  relations,
4497
+ events: normalizedEvents,
3808
4498
  objects,
3809
4499
  diagnostics
3810
4500
  };
@@ -3834,13 +4524,13 @@ var WorldOrbit = (() => {
3834
4524
  return document2;
3835
4525
  }
3836
4526
  function assertDraftSchemaHeader(tokens, line) {
3837
- if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1"].includes(tokens[1].value.toLowerCase())) {
3838
- throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
4527
+ if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1", "2.5"].includes(tokens[1].value.toLowerCase())) {
4528
+ throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", "schema 2.5", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
3839
4529
  }
3840
4530
  const version = tokens[1].value.toLowerCase();
3841
- return version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
4531
+ return version === "2.5" ? "2.5" : version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
3842
4532
  }
3843
- function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, flags) {
4533
+ function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
3844
4534
  const keyword = tokens[0]?.value.toLowerCase();
3845
4535
  switch (keyword) {
3846
4536
  case "system":
@@ -3858,6 +4548,8 @@ var WorldOrbit = (() => {
3858
4548
  return {
3859
4549
  kind: "defaults",
3860
4550
  system,
4551
+ sourceSchemaVersion,
4552
+ diagnostics,
3861
4553
  seenFields: /* @__PURE__ */ new Set()
3862
4554
  };
3863
4555
  case "atlas":
@@ -3877,7 +4569,7 @@ var WorldOrbit = (() => {
3877
4569
  if (!system) {
3878
4570
  throw new WorldOrbitError('Atlas section "viewpoint" requires a preceding system declaration', line, tokens[0].column);
3879
4571
  }
3880
- return startViewpointSection(tokens, line, system, viewpointIds);
4572
+ return startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics);
3881
4573
  case "annotation":
3882
4574
  if (!system) {
3883
4575
  throw new WorldOrbitError('Atlas section "annotation" requires a preceding system declaration', line, tokens[0].column);
@@ -3889,6 +4581,9 @@ var WorldOrbit = (() => {
3889
4581
  case "relation":
3890
4582
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "relation", { line, column: tokens[0].column });
3891
4583
  return startRelationSection(tokens, line, relations, relationIds);
4584
+ case "event":
4585
+ warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "event", { line, column: tokens[0].column });
4586
+ return startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics);
3892
4587
  case "object":
3893
4588
  return startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes);
3894
4589
  default:
@@ -3925,7 +4620,7 @@ var WorldOrbit = (() => {
3925
4620
  seenFields: /* @__PURE__ */ new Set()
3926
4621
  };
3927
4622
  }
3928
- function startViewpointSection(tokens, line, system, viewpointIds) {
4623
+ function startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics) {
3929
4624
  if (tokens.length !== 2) {
3930
4625
  throw new WorldOrbitError("Invalid viewpoint declaration", line, tokens[0]?.column ?? 1);
3931
4626
  }
@@ -3942,10 +4637,12 @@ var WorldOrbit = (() => {
3942
4637
  summary: "",
3943
4638
  focusObjectId: null,
3944
4639
  selectedObjectId: null,
4640
+ events: [],
3945
4641
  projection: system.defaults.view,
3946
4642
  preset: system.defaults.preset,
3947
4643
  zoom: null,
3948
4644
  rotationDeg: 0,
4645
+ camera: null,
3949
4646
  layers: {},
3950
4647
  filter: null
3951
4648
  };
@@ -3954,10 +4651,15 @@ var WorldOrbit = (() => {
3954
4651
  return {
3955
4652
  kind: "viewpoint",
3956
4653
  viewpoint,
4654
+ sourceSchemaVersion,
4655
+ diagnostics,
3957
4656
  seenFields: /* @__PURE__ */ new Set(),
3958
4657
  inFilter: false,
3959
4658
  filterIndent: null,
3960
- seenFilterFields: /* @__PURE__ */ new Set()
4659
+ seenFilterFields: /* @__PURE__ */ new Set(),
4660
+ inCamera: false,
4661
+ cameraIndent: null,
4662
+ seenCameraFields: /* @__PURE__ */ new Set()
3961
4663
  };
3962
4664
  }
3963
4665
  function startAnnotationSection(tokens, line, system, annotationIds) {
@@ -4044,6 +4746,51 @@ var WorldOrbit = (() => {
4044
4746
  seenFields: /* @__PURE__ */ new Set()
4045
4747
  };
4046
4748
  }
4749
+ function startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics) {
4750
+ if (tokens.length !== 2) {
4751
+ throw new WorldOrbitError("Invalid event declaration", line, tokens[0]?.column ?? 1);
4752
+ }
4753
+ const id = normalizeIdentifier2(tokens[1].value);
4754
+ if (!id) {
4755
+ throw new WorldOrbitError("Event id must not be empty", line, tokens[1].column);
4756
+ }
4757
+ if (eventIds.has(id)) {
4758
+ throw new WorldOrbitError(`Duplicate event id "${id}"`, line, tokens[1].column);
4759
+ }
4760
+ const event = {
4761
+ id,
4762
+ kind: "",
4763
+ label: humanizeIdentifier3(id),
4764
+ summary: null,
4765
+ targetObjectId: null,
4766
+ participantObjectIds: [],
4767
+ timing: null,
4768
+ visibility: null,
4769
+ epoch: null,
4770
+ referencePlane: null,
4771
+ tags: [],
4772
+ color: null,
4773
+ hidden: false,
4774
+ positions: []
4775
+ };
4776
+ const rawPoses = [];
4777
+ events.push(event);
4778
+ eventPoseNodes.set(id, rawPoses);
4779
+ eventIds.add(id);
4780
+ return {
4781
+ kind: "event",
4782
+ event,
4783
+ sourceSchemaVersion,
4784
+ diagnostics,
4785
+ seenFields: /* @__PURE__ */ new Set(),
4786
+ rawPoses,
4787
+ inPositions: false,
4788
+ positionsIndent: null,
4789
+ activePose: null,
4790
+ poseIndent: null,
4791
+ activePoseSeenFields: /* @__PURE__ */ new Set()
4792
+ };
4793
+ }
4047
4794
  function startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes) {
4048
4795
  if (tokens.length < 3) {
4049
4796
  throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
@@ -4100,6 +4847,9 @@ var WorldOrbit = (() => {
4100
4847
  case "relation":
4101
4848
  applyRelationField(section, tokens, line);
4102
4849
  return;
4850
+ case "event":
4851
+ applyEventField(section, indent, tokens, line);
4852
+ return;
4103
4853
  case "object":
4104
4854
  applyObjectField(section, indent, tokens, line);
4105
4855
  return;
@@ -4142,6 +4892,12 @@ var WorldOrbit = (() => {
4142
4892
  const value = joinFieldValue(tokens, line);
4143
4893
  switch (key) {
4144
4894
  case "view":
4895
+ if (isSchema25Projection(value)) {
4896
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "defaults.view", {
4897
+ line,
4898
+ column: tokens[0].column
4899
+ });
4900
+ }
4145
4901
  section.system.defaults.view = parseProjectionValue(value, line, tokens[0].column);
4146
4902
  return;
4147
4903
  case "scale":
@@ -4181,14 +4937,36 @@ var WorldOrbit = (() => {
4181
4937
  throw new WorldOrbitError(`Unknown atlas field "${tokens[0].value}"`, line, tokens[0].column);
4182
4938
  }
4183
4939
  function applyViewpointField2(section, indent, tokens, line) {
4940
+ if (section.inCamera && indent <= (section.cameraIndent ?? 0)) {
4941
+ section.inCamera = false;
4942
+ section.cameraIndent = null;
4943
+ }
4184
4944
  if (section.inFilter && indent <= (section.filterIndent ?? 0)) {
4185
4945
  section.inFilter = false;
4186
4946
  section.filterIndent = null;
4187
4947
  }
4948
+ if (section.inCamera) {
4949
+ applyViewpointCameraField(section, tokens, line);
4950
+ return;
4951
+ }
4188
4952
  if (section.inFilter) {
4189
4953
  applyViewpointFilterField(section, tokens, line);
4190
4954
  return;
4191
4955
  }
4956
+ if (tokens.length === 1 && tokens[0].value.toLowerCase() === "camera") {
4957
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
4958
+ line,
4959
+ column: tokens[0].column
4960
+ });
4961
+ if (section.seenFields.has("camera")) {
4962
+ throw new WorldOrbitError('Duplicate viewpoint field "camera"', line, tokens[0].column);
4963
+ }
4964
+ section.seenFields.add("camera");
4965
+ section.inCamera = true;
4966
+ section.cameraIndent = indent;
4967
+ section.viewpoint.camera = section.viewpoint.camera ?? createEmptyViewCamera2();
4968
+ return;
4969
+ }
4192
4970
  if (tokens.length === 1 && tokens[0].value.toLowerCase() === "filter") {
4193
4971
  if (section.seenFields.has("filter")) {
4194
4972
  throw new WorldOrbitError('Duplicate viewpoint field "filter"', line, tokens[0].column);
@@ -4214,6 +4992,12 @@ var WorldOrbit = (() => {
4214
4992
  section.viewpoint.selectedObjectId = value;
4215
4993
  return;
4216
4994
  case "projection":
4995
+ if (isSchema25Projection(value)) {
4996
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "projection", {
4997
+ line,
4998
+ column: tokens[0].column
4999
+ });
5000
+ }
4217
5001
  section.viewpoint.projection = parseProjectionValue(value, line, tokens[0].column);
4218
5002
  return;
4219
5003
  case "preset":
@@ -4225,13 +5009,49 @@ var WorldOrbit = (() => {
4225
5009
  case "rotation":
4226
5010
  section.viewpoint.rotationDeg = parseFiniteNumber2(value, line, tokens[0].column, "rotation");
4227
5011
  return;
5012
+ case "camera":
5013
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
5014
+ line,
5015
+ column: tokens[0].column
5016
+ });
5017
+ section.viewpoint.camera = parseInlineViewCamera(tokens.slice(1), line, section.viewpoint.camera);
5018
+ return;
4228
5019
  case "layers":
4229
- section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line);
5020
+ section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line, section.sourceSchemaVersion, section.diagnostics);
5021
+ return;
5022
+ case "events":
5023
+ warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.events", {
5024
+ line,
5025
+ column: tokens[0].column
5026
+ });
5027
+ section.viewpoint.events = parseTokenList(tokens.slice(1), line, "events");
4230
5028
  return;
4231
5029
  default:
4232
5030
  throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
4233
5031
  }
4234
5032
  }
5033
+ function applyViewpointCameraField(section, tokens, line) {
5034
+ const key = requireUniqueField(tokens, section.seenCameraFields, line);
5035
+ const value = joinFieldValue(tokens, line);
5036
+ const camera = section.viewpoint.camera ?? createEmptyViewCamera2();
5037
+ switch (key) {
5038
+ case "azimuth":
5039
+ camera.azimuth = parseFiniteNumber2(value, line, tokens[0].column, "camera.azimuth");
5040
+ break;
5041
+ case "elevation":
5042
+ camera.elevation = parseFiniteNumber2(value, line, tokens[0].column, "camera.elevation");
5043
+ break;
5044
+ case "roll":
5045
+ camera.roll = parseFiniteNumber2(value, line, tokens[0].column, "camera.roll");
5046
+ break;
5047
+ case "distance":
5048
+ camera.distance = parsePositiveNumber2(value, line, tokens[0].column, "camera.distance");
5049
+ break;
5050
+ default:
5051
+ throw new WorldOrbitError(`Unknown viewpoint camera field "${tokens[0].value}"`, line, tokens[0].column);
5052
+ }
5053
+ section.viewpoint.camera = camera;
5054
+ }
4235
5055
  function applyViewpointFilterField(section, tokens, line) {
4236
5056
  const key = requireUniqueField(tokens, section.seenFilterFields, line);
4237
5057
  const filter = section.viewpoint.filter ?? createEmptyViewpointFilter2();
@@ -4331,6 +5151,126 @@ var WorldOrbit = (() => {
4331
5151
  throw new WorldOrbitError(`Unknown relation field "${tokens[0].value}"`, line, tokens[0].column);
4332
5152
  }
4333
5153
  }
5154
+ function applyEventField(section, indent, tokens, line) {
5155
+ if (section.activePose && indent <= (section.poseIndent ?? 0)) {
5156
+ section.activePose = null;
5157
+ section.poseIndent = null;
5158
+ section.activePoseSeenFields.clear();
5159
+ }
5160
+ if (!section.activePose && section.inPositions && indent <= (section.positionsIndent ?? 0)) {
5161
+ section.inPositions = false;
5162
+ section.positionsIndent = null;
5163
+ }
5164
+ if (section.activePose) {
5165
+ if (tokens[0]?.value === "epoch" || tokens[0]?.value === "referencePlane") {
5166
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, `pose.${tokens[0].value}`, {
5167
+ line,
5168
+ column: tokens[0]?.column ?? 1
5169
+ });
5170
+ }
5171
+ section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
5172
+ return;
5173
+ }
5174
+ if (section.inPositions) {
5175
+ if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "pose") {
5176
+ throw new WorldOrbitError(`Unknown event positions field "${tokens[0].value}"`, line, tokens[0]?.column ?? 1);
5177
+ }
5178
+ const objectId = tokens[1].value;
5179
+ if (!objectId.trim()) {
5180
+ throw new WorldOrbitError("Event pose object id must not be empty", line, tokens[1].column);
5181
+ }
5182
+ const rawPose = {
5183
+ objectId,
5184
+ fields: [],
5185
+ location: { line, column: tokens[0].column }
5186
+ };
5187
+ section.rawPoses.push(rawPose);
5188
+ section.activePose = rawPose;
5189
+ section.poseIndent = indent;
5190
+ section.activePoseSeenFields = /* @__PURE__ */ new Set();
5191
+ return;
5192
+ }
5193
+ if (tokens.length === 1 && tokens[0].value.toLowerCase() === "positions") {
5194
+ if (section.seenFields.has("positions")) {
5195
+ throw new WorldOrbitError('Duplicate event field "positions"', line, tokens[0].column);
5196
+ }
5197
+ section.seenFields.add("positions");
5198
+ section.inPositions = true;
5199
+ section.positionsIndent = indent;
5200
+ return;
5201
+ }
5202
+ const key = requireUniqueField(tokens, section.seenFields, line);
5203
+ switch (key) {
5204
+ case "kind":
5205
+ section.event.kind = joinFieldValue(tokens, line);
5206
+ return;
5207
+ case "label":
5208
+ section.event.label = joinFieldValue(tokens, line);
5209
+ return;
5210
+ case "summary":
5211
+ section.event.summary = joinFieldValue(tokens, line);
5212
+ return;
5213
+ case "target":
5214
+ section.event.targetObjectId = joinFieldValue(tokens, line);
5215
+ return;
5216
+ case "participants":
5217
+ section.event.participantObjectIds = parseTokenList(tokens.slice(1), line, "participants");
5218
+ return;
5219
+ case "timing":
5220
+ section.event.timing = joinFieldValue(tokens, line);
5221
+ return;
5222
+ case "visibility":
5223
+ section.event.visibility = joinFieldValue(tokens, line);
5224
+ return;
5225
+ case "epoch":
5226
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.epoch", {
5227
+ line,
5228
+ column: tokens[0].column
5229
+ });
5230
+ section.event.epoch = joinFieldValue(tokens, line);
5231
+ return;
5232
+ case "referenceplane":
5233
+ warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.referencePlane", {
5234
+ line,
5235
+ column: tokens[0].column
5236
+ });
5237
+ section.event.referencePlane = joinFieldValue(tokens, line);
5238
+ return;
5239
+ case "tags":
5240
+ section.event.tags = parseTokenList(tokens.slice(1), line, "tags");
5241
+ return;
5242
+ case "color":
5243
+ section.event.color = joinFieldValue(tokens, line);
5244
+ return;
5245
+ case "hidden":
5246
+ section.event.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
5247
+ line,
5248
+ column: tokens[0].column
5249
+ });
5250
+ return;
5251
+ default:
5252
+ throw new WorldOrbitError(`Unknown event field "${tokens[0].value}"`, line, tokens[0].column);
5253
+ }
5254
+ }
5255
+ function parseEventPoseField(tokens, line, seenFields) {
5256
+ if (tokens.length < 2) {
5257
+ throw new WorldOrbitError("Invalid event pose field line", line, tokens[0]?.column ?? 1);
5258
+ }
5259
+ const key = tokens[0].value;
5260
+ if (!EVENT_POSE_FIELD_KEYS.has(key)) {
5261
+ throw new WorldOrbitError(`Unknown event pose field "${key}"`, line, tokens[0].column);
5262
+ }
5263
+ if (seenFields.has(key)) {
5264
+ throw new WorldOrbitError(`Duplicate event pose field "${key}"`, line, tokens[0].column);
5265
+ }
5266
+ seenFields.add(key);
5267
+ return {
5268
+ type: "field",
5269
+ key,
5270
+ values: tokens.slice(1).map((token) => token.value),
5271
+ location: { line, column: tokens[0].column }
5272
+ };
5273
+ }
4334
5274
  function applyObjectField(section, indent, tokens, line) {
4335
5275
  if (section.activeBlock && indent <= (section.blockIndent ?? 0)) {
4336
5276
  section.activeBlock = null;
@@ -4389,7 +5329,7 @@ var WorldOrbit = (() => {
4389
5329
  function parseObjectTypeTokens(tokens, line) {
4390
5330
  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");
4391
5331
  }
4392
- function parseLayerTokens(tokens, line) {
5332
+ function parseLayerTokens(tokens, line, sourceSchemaVersion, diagnostics) {
4393
5333
  const layers = {};
4394
5334
  for (const token of parseTokenList(tokens, line, "layers")) {
4395
5335
  const enabled = !token.startsWith("-") && !token.startsWith("!");
@@ -4399,7 +5339,13 @@ var WorldOrbit = (() => {
4399
5339
  layers["orbits-front"] = enabled;
4400
5340
  continue;
4401
5341
  }
4402
- if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "objects" || raw === "labels" || raw === "metadata") {
5342
+ if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "events" || raw === "objects" || raw === "labels" || raw === "metadata") {
5343
+ if (raw === "events" && sourceSchemaVersion && diagnostics) {
5344
+ warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "layers.events", {
5345
+ line,
5346
+ column: tokens[0]?.column ?? 1
5347
+ });
5348
+ }
4403
5349
  layers[raw] = enabled;
4404
5350
  }
4405
5351
  }
@@ -4417,11 +5363,15 @@ var WorldOrbit = (() => {
4417
5363
  }
4418
5364
  function parseProjectionValue(value, line, column) {
4419
5365
  const normalized = value.toLowerCase();
4420
- if (normalized !== "topdown" && normalized !== "isometric") {
5366
+ if (normalized !== "topdown" && normalized !== "isometric" && normalized !== "orthographic" && normalized !== "perspective") {
4421
5367
  throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
4422
5368
  }
4423
5369
  return normalized;
4424
5370
  }
5371
+ function isSchema25Projection(value) {
5372
+ const normalized = value.toLowerCase();
5373
+ return normalized === "orthographic" || normalized === "perspective";
5374
+ }
4425
5375
  function parsePresetValue(value, line, column) {
4426
5376
  const normalized = value.toLowerCase();
4427
5377
  if (normalized === "diagram" || normalized === "presentation" || normalized === "atlas-card" || normalized === "markdown") {
@@ -4451,6 +5401,48 @@ var WorldOrbit = (() => {
4451
5401
  groupIds: []
4452
5402
  };
4453
5403
  }
5404
+ function createEmptyViewCamera2() {
5405
+ return {
5406
+ azimuth: null,
5407
+ elevation: null,
5408
+ roll: null,
5409
+ distance: null
5410
+ };
5411
+ }
5412
+ function parseInlineViewCamera(tokens, line, current) {
5413
+ if (tokens.length === 0 || tokens.length % 2 !== 0) {
5414
+ throw new WorldOrbitError('Field "camera" expects "<field> <value>" pairs', line, tokens[0]?.column ?? 1);
5415
+ }
5416
+ const camera = current ? { ...current } : createEmptyViewCamera2();
5417
+ const seen = /* @__PURE__ */ new Set();
5418
+ for (let index = 0; index < tokens.length; index += 2) {
5419
+ const fieldToken = tokens[index];
5420
+ const valueToken = tokens[index + 1];
5421
+ const key = fieldToken.value.toLowerCase();
5422
+ if (seen.has(key)) {
5423
+ throw new WorldOrbitError(`Duplicate viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
5424
+ }
5425
+ seen.add(key);
5426
+ const value = valueToken.value;
5427
+ switch (key) {
5428
+ case "azimuth":
5429
+ camera.azimuth = parseFiniteNumber2(value, line, fieldToken.column, "camera.azimuth");
5430
+ break;
5431
+ case "elevation":
5432
+ camera.elevation = parseFiniteNumber2(value, line, fieldToken.column, "camera.elevation");
5433
+ break;
5434
+ case "roll":
5435
+ camera.roll = parseFiniteNumber2(value, line, fieldToken.column, "camera.roll");
5436
+ break;
5437
+ case "distance":
5438
+ camera.distance = parsePositiveNumber2(value, line, fieldToken.column, "camera.distance");
5439
+ break;
5440
+ default:
5441
+ throw new WorldOrbitError(`Unknown viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
5442
+ }
5443
+ }
5444
+ return camera;
5445
+ }
4454
5446
  function parseInlineObjectFields(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
4455
5447
  const fields = [];
4456
5448
  let index = 0;
@@ -4538,7 +5530,7 @@ var WorldOrbit = (() => {
4538
5530
  }
4539
5531
  function normalizeDraftObject(node, sourceSchemaVersion, diagnostics) {
4540
5532
  const fieldMap = collectDraftFields(node.fields);
4541
- const placement = extractDraftPlacement(node.objectType, fieldMap);
5533
+ const placement = extractPlacementFromFieldMap(fieldMap);
4542
5534
  const properties = normalizeDraftProperties(node.objectType, fieldMap);
4543
5535
  const groups = parseOptionalTokenList(fieldMap.get("groups")?.[0]);
4544
5536
  const epoch = parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]);
@@ -4583,21 +5575,41 @@ var WorldOrbit = (() => {
4583
5575
  object.tolerances = tolerances;
4584
5576
  if (typedBlocks && Object.keys(typedBlocks).length > 0)
4585
5577
  object.typedBlocks = typedBlocks;
4586
- if (sourceSchemaVersion !== "2.1") {
5578
+ if (isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
4587
5579
  if (object.groups || object.epoch || object.referencePlane || object.tidalLock !== void 0 || object.resonance || object.renderHints || object.deriveRules?.length || object.validationRules?.length || object.lockedFields?.length || object.tolerances?.length || object.typedBlocks) {
4588
5580
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
4589
5581
  }
4590
5582
  }
4591
5583
  return object;
4592
5584
  }
4593
- function collectDraftFields(fields) {
5585
+ function normalizeDraftEvent(event, rawPoses) {
5586
+ return {
5587
+ ...event,
5588
+ participantObjectIds: [...new Set(event.participantObjectIds)],
5589
+ tags: [...new Set(event.tags)],
5590
+ positions: rawPoses.map((pose) => normalizeDraftEventPose(pose))
5591
+ };
5592
+ }
5593
+ function normalizeDraftEventPose(rawPose) {
5594
+ const fieldMap = collectDraftFields(rawPose.fields, "event-pose");
5595
+ const placement = extractPlacementFromFieldMap(fieldMap);
5596
+ return {
5597
+ objectId: rawPose.objectId,
5598
+ placement,
5599
+ inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
5600
+ outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer"),
5601
+ epoch: parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]),
5602
+ referencePlane: parseOptionalJoinedValue(fieldMap.get("referencePlane")?.[0])
5603
+ };
5604
+ }
5605
+ function collectDraftFields(fields, _mode = "object") {
4594
5606
  const grouped = /* @__PURE__ */ new Map();
4595
5607
  for (const field of fields) {
4596
5608
  const spec = getDraftObjectFieldSpec(field.key);
4597
- if (!spec) {
5609
+ if (!spec && !EVENT_POSE_FIELD_KEYS.has(field.key)) {
4598
5610
  throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
4599
5611
  }
4600
- if (!spec.allowRepeat && grouped.has(field.key)) {
5612
+ if (!spec?.allowRepeat && grouped.has(field.key)) {
4601
5613
  throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
4602
5614
  }
4603
5615
  const existing = grouped.get(field.key) ?? [];
@@ -4606,7 +5618,7 @@ var WorldOrbit = (() => {
4606
5618
  }
4607
5619
  return grouped;
4608
5620
  }
4609
- function extractDraftPlacement(objectType, fieldMap) {
5621
+ function extractPlacementFromFieldMap(fieldMap) {
4610
5622
  const orbitField = fieldMap.get("orbit")?.[0];
4611
5623
  const atField = fieldMap.get("at")?.[0];
4612
5624
  const surfaceField = fieldMap.get("surface")?.[0];
@@ -4774,7 +5786,7 @@ var WorldOrbit = (() => {
4774
5786
  }
4775
5787
  }
4776
5788
  function warnIfSchema21Feature(sourceSchemaVersion, diagnostics, featureName, location) {
4777
- if (sourceSchemaVersion === "2.1") {
5789
+ if (!isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
4778
5790
  return;
4779
5791
  }
4780
5792
  diagnostics.push({
@@ -4786,6 +5798,34 @@ var WorldOrbit = (() => {
4786
5798
  column: location.column
4787
5799
  });
4788
5800
  }
5801
+ function warnIfSchema25Feature(sourceSchemaVersion, diagnostics, featureName, location) {
5802
+ if (!isSchemaOlderThan(sourceSchemaVersion, "2.5")) {
5803
+ return;
5804
+ }
5805
+ diagnostics.push({
5806
+ code: "parse.schema25.featureCompatibility",
5807
+ severity: "warning",
5808
+ source: "parse",
5809
+ message: `Feature "${featureName}" requires schema 2.5; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
5810
+ line: location.line,
5811
+ column: location.column
5812
+ });
5813
+ }
5814
+ function isSchemaOlderThan(sourceSchemaVersion, requiredVersion) {
5815
+ return schemaVersionRank(sourceSchemaVersion) < schemaVersionRank(requiredVersion);
5816
+ }
5817
+ function schemaVersionRank(version) {
5818
+ switch (version) {
5819
+ case "2.0-draft":
5820
+ return 0;
5821
+ case "2.0":
5822
+ return 1;
5823
+ case "2.1":
5824
+ return 2;
5825
+ case "2.5":
5826
+ return 3;
5827
+ }
5828
+ }
4789
5829
  function preprocessAtlasSource(source) {
4790
5830
  const chars = [...source];
4791
5831
  const comments = [];
@@ -4873,7 +5913,7 @@ var WorldOrbit = (() => {
4873
5913
  }
4874
5914
 
4875
5915
  // packages/core/dist/atlas-edit.js
4876
- function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.0") {
5916
+ function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.5") {
4877
5917
  return {
4878
5918
  format: "worldorbit",
4879
5919
  version,
@@ -4899,6 +5939,7 @@ var WorldOrbit = (() => {
4899
5939
  },
4900
5940
  groups: [],
4901
5941
  relations: [],
5942
+ events: [],
4902
5943
  objects: [],
4903
5944
  diagnostics: []
4904
5945
  };
@@ -4916,6 +5957,10 @@ var WorldOrbit = (() => {
4916
5957
  return path.key ? document2.system?.atlasMetadata[path.key] ?? null : null;
4917
5958
  case "group":
4918
5959
  return path.id ? findGroup(document2, path.id) : null;
5960
+ case "event":
5961
+ return path.id ? findEvent(document2, path.id) : null;
5962
+ case "event-pose":
5963
+ return path.id && path.key ? findEventPose(document2, path.id, path.key) : null;
4919
5964
  case "object":
4920
5965
  return path.id ? findObject(document2, path.id) : null;
4921
5966
  case "viewpoint":
@@ -4945,6 +5990,19 @@ var WorldOrbit = (() => {
4945
5990
  next.groups = next.groups.filter((group) => group.id !== path.id);
4946
5991
  }
4947
5992
  return next;
5993
+ case "event":
5994
+ if (path.id) {
5995
+ next.events = next.events.filter((event) => event.id !== path.id);
5996
+ }
5997
+ return next;
5998
+ case "event-pose":
5999
+ if (path.id && path.key) {
6000
+ const event = findEvent(next, path.id);
6001
+ if (event) {
6002
+ event.positions = event.positions.filter((pose) => pose.objectId !== path.key);
6003
+ }
6004
+ }
6005
+ return next;
4948
6006
  case "viewpoint":
4949
6007
  if (path.id) {
4950
6008
  system.viewpoints = system.viewpoints.filter((viewpoint) => viewpoint.id !== path.id);
@@ -5013,6 +6071,22 @@ var WorldOrbit = (() => {
5013
6071
  };
5014
6072
  }
5015
6073
  }
6074
+ if (diagnostic.field?.startsWith("event.")) {
6075
+ const parts = diagnostic.field.split(".");
6076
+ if (parts[1] && findEvent(document2, parts[1])) {
6077
+ if (parts[2] === "pose" && parts[3] && findEventPose(document2, parts[1], parts[3])) {
6078
+ return {
6079
+ kind: "event-pose",
6080
+ id: parts[1],
6081
+ key: parts[3]
6082
+ };
6083
+ }
6084
+ return {
6085
+ kind: "event",
6086
+ id: parts[1]
6087
+ };
6088
+ }
6089
+ }
5016
6090
  if (diagnostic.field && diagnostic.field in ensureSystem(document2).atlasMetadata) {
5017
6091
  return {
5018
6092
  kind: "metadata",
@@ -5044,6 +6118,12 @@ var WorldOrbit = (() => {
5044
6118
  function findRelation(document2, relationId) {
5045
6119
  return document2.relations.find((relation) => relation.id === relationId) ?? null;
5046
6120
  }
6121
+ function findEvent(document2, eventId) {
6122
+ return document2.events.find((event) => event.id === eventId) ?? null;
6123
+ }
6124
+ function findEventPose(document2, eventId, objectId) {
6125
+ return findEvent(document2, eventId)?.positions.find((pose) => pose.objectId === objectId) ?? null;
6126
+ }
5047
6127
  function findViewpoint(system, viewpointId) {
5048
6128
  return system?.viewpoints.find((viewpoint) => viewpoint.id === viewpointId) ?? null;
5049
6129
  }
@@ -5052,8 +6132,9 @@ var WorldOrbit = (() => {
5052
6132
  }
5053
6133
 
5054
6134
  // packages/core/dist/load.js
5055
- var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1)?$/i;
6135
+ var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1|\.5)?$/i;
5056
6136
  var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
6137
+ var ATLAS_SCHEMA_25_PATTERN = /^schema\s+2\.5$/i;
5057
6138
  var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
5058
6139
  function detectWorldOrbitSchemaVersion(source) {
5059
6140
  for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
@@ -5067,6 +6148,9 @@ var WorldOrbit = (() => {
5067
6148
  if (ATLAS_SCHEMA_21_PATTERN.test(trimmed)) {
5068
6149
  return "2.1";
5069
6150
  }
6151
+ if (ATLAS_SCHEMA_25_PATTERN.test(trimmed)) {
6152
+ return "2.5";
6153
+ }
5070
6154
  if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
5071
6155
  return "2.0";
5072
6156
  }
@@ -5127,7 +6211,7 @@ var WorldOrbit = (() => {
5127
6211
  }
5128
6212
  function loadWorldOrbitSourceWithDiagnostics(source) {
5129
6213
  const schemaVersion = detectWorldOrbitSchemaVersion(source);
5130
- if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1") {
6214
+ if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5") {
5131
6215
  return loadAtlasSourceWithDiagnostics(source, schemaVersion);
5132
6216
  }
5133
6217
  let ast;
@@ -5221,6 +6305,7 @@ var WorldOrbit = (() => {
5221
6305
  background: true,
5222
6306
  guides: true,
5223
6307
  relations: true,
6308
+ events: true,
5224
6309
  orbits: true,
5225
6310
  objects: true,
5226
6311
  labels: true,
@@ -5367,14 +6452,17 @@ var WorldOrbit = (() => {
5367
6452
  }
5368
6453
  function createAtlasStateSnapshot(viewerState, renderOptions, filter, viewpointId) {
5369
6454
  return {
5370
- version: "2.0",
6455
+ version: "2.5",
5371
6456
  viewpointId,
6457
+ activeEventId: renderOptions.activeEventId ?? null,
5372
6458
  viewerState: { ...viewerState },
5373
6459
  renderOptions: {
5374
6460
  preset: renderOptions.preset,
5375
6461
  projection: renderOptions.projection,
6462
+ camera: renderOptions.camera ? { ...renderOptions.camera } : null,
5376
6463
  layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
5377
- scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0
6464
+ scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
6465
+ activeEventId: renderOptions.activeEventId ?? null
5378
6466
  },
5379
6467
  filter: normalizeViewerFilter(filter)
5380
6468
  };
@@ -5385,8 +6473,9 @@ var WorldOrbit = (() => {
5385
6473
  function deserializeViewerAtlasState(serialized) {
5386
6474
  const raw = JSON.parse(decodeURIComponent(serialized));
5387
6475
  return {
5388
- version: "2.0",
6476
+ version: raw.version === "2.0" ? "2.0" : "2.5",
5389
6477
  viewpointId: raw.viewpointId ?? null,
6478
+ activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null,
5390
6479
  viewerState: {
5391
6480
  scale: raw.viewerState?.scale ?? 1,
5392
6481
  rotationDeg: raw.viewerState?.rotationDeg ?? 0,
@@ -5397,8 +6486,10 @@ var WorldOrbit = (() => {
5397
6486
  renderOptions: {
5398
6487
  preset: raw.renderOptions?.preset,
5399
6488
  projection: raw.renderOptions?.projection,
6489
+ camera: raw.renderOptions?.camera ? { ...raw.renderOptions.camera } : null,
5400
6490
  layers: raw.renderOptions?.layers ? { ...raw.renderOptions.layers } : void 0,
5401
- scaleModel: raw.renderOptions?.scaleModel ? { ...raw.renderOptions.scaleModel } : void 0
6491
+ scaleModel: raw.renderOptions?.scaleModel ? { ...raw.renderOptions.scaleModel } : void 0,
6492
+ activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null
5402
6493
  },
5403
6494
  filter: normalizeViewerFilter(raw.filter ?? null)
5404
6495
  };
@@ -5413,8 +6504,10 @@ var WorldOrbit = (() => {
5413
6504
  viewerState: { ...atlasState.viewerState },
5414
6505
  renderOptions: {
5415
6506
  ...atlasState.renderOptions,
6507
+ camera: atlasState.renderOptions.camera ? { ...atlasState.renderOptions.camera } : null,
5416
6508
  layers: atlasState.renderOptions.layers ? { ...atlasState.renderOptions.layers } : void 0,
5417
- scaleModel: atlasState.renderOptions.scaleModel ? { ...atlasState.renderOptions.scaleModel } : void 0
6509
+ scaleModel: atlasState.renderOptions.scaleModel ? { ...atlasState.renderOptions.scaleModel } : void 0,
6510
+ activeEventId: atlasState.renderOptions.activeEventId ?? null
5418
6511
  },
5419
6512
  filter: atlasState.filter ? { ...atlasState.filter } : null
5420
6513
  }
@@ -5432,6 +6525,7 @@ var WorldOrbit = (() => {
5432
6525
  background: viewpoint.layers.background,
5433
6526
  guides: viewpoint.layers.guides,
5434
6527
  relations: viewpoint.layers.relations,
6528
+ events: viewpoint.layers.events,
5435
6529
  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,
5436
6530
  objects: viewpoint.layers.objects,
5437
6531
  labels: viewpoint.layers.labels,
@@ -5718,6 +6812,7 @@ var WorldOrbit = (() => {
5718
6812
  const orbitMarkup = layers.orbits ? renderOrbitLayer(scene, visibleObjectIds, layers.structures) : { back: "", front: "" };
5719
6813
  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("") : "";
5720
6814
  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("") : "";
6815
+ const eventMarkup = layers.events ? scene.events.filter((event) => !event.hidden).map((event) => renderSceneEventOverlay(scene, event, visibleObjectIds, theme)).join("") : "";
5721
6816
  const objectMarkup = layers.objects ? visibleObjects.map((object) => renderSceneObject(object, options.selectedObjectId ?? null, theme)).join("") : "";
5722
6817
  const labelMarkup = layers.labels ? visibleLabels.map((label) => renderSceneLabel(scene, label, options.selectedObjectId ?? null)).join("") : "";
5723
6818
  const metadataMarkup = layers.metadata ? `<text class="wo-title" x="56" y="64">${escapeXml(scene.title)}</text>
@@ -5753,6 +6848,9 @@ var WorldOrbit = (() => {
5753
6848
  .wo-orbit-front { opacity: 0.9; }
5754
6849
  .wo-orbit-band { stroke: ${theme.orbitBand}; stroke-linecap: round; }
5755
6850
  .wo-relation { stroke: ${theme.relation}; stroke-width: 2; stroke-dasharray: 10 6; }
6851
+ .wo-event-line { stroke: ${theme.accent}; stroke-width: 1.6; stroke-dasharray: 5 5; opacity: 0.72; }
6852
+ .wo-event-node { fill: ${theme.accent}; stroke: ${theme.selected}; stroke-width: 1.4; opacity: 0.92; }
6853
+ .wo-event-label { fill: ${theme.accent}; font-family: ${theme.fontFamily}; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
5756
6854
  .wo-leader { stroke: ${theme.leader}; stroke-width: 1.5; stroke-dasharray: 6 5; }
5757
6855
  .wo-label { fill: ${theme.ink}; font-family: ${theme.fontFamily}; font-weight: 600; letter-spacing: 0.02em; }
5758
6856
  .wo-label-secondary { fill: ${theme.muted}; font-family: ${theme.fontFamily}; font-weight: 500; }
@@ -5787,6 +6885,7 @@ var WorldOrbit = (() => {
5787
6885
  ${layers.orbits ? `<g data-layer-id="orbits-back">${orbitMarkup.back}</g>` : ""}
5788
6886
  ${layers.guides ? `<g data-layer-id="guides">${leaderMarkup}</g>` : ""}
5789
6887
  ${layers.relations ? `<g data-layer-id="relations">${relationMarkup}</g>` : ""}
6888
+ ${layers.events ? `<g data-layer-id="events">${eventMarkup}</g>` : ""}
5790
6889
  ${layers.objects ? `<g data-layer-id="objects">${objectMarkup}</g>` : ""}
5791
6890
  ${layers.orbits ? `<g data-layer-id="orbits-front">${orbitMarkup.front}</g>` : ""}
5792
6891
  ${layers.labels ? `<g data-layer-id="labels">${labelMarkup}</g>` : ""}
@@ -5794,6 +6893,20 @@ var WorldOrbit = (() => {
5794
6893
  </g>
5795
6894
  </g>
5796
6895
  </svg>`;
6896
+ }
6897
+ function renderSceneEventOverlay(scene, event, visibleObjectIds, theme) {
6898
+ const participants = event.objectIds.filter((objectId) => visibleObjectIds.has(objectId)).map((objectId) => scene.objects.find((object) => object.objectId === objectId && !object.hidden)).filter(Boolean);
6899
+ if (participants.length === 0) {
6900
+ return "";
6901
+ }
6902
+ const stroke = event.event.color || theme.accent;
6903
+ const label = event.event.label || event.event.id;
6904
+ 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("");
6905
+ return `<g class="wo-event" data-render-id="${escapeXml(event.renderId)}" data-event-id="${escapeAttribute(event.eventId)}">
6906
+ ${lineMarkup}
6907
+ <circle class="wo-event-node" cx="${event.x}" cy="${event.y}" r="5" fill="${escapeAttribute(stroke)}" />
6908
+ <text class="wo-event-label" x="${event.x}" y="${event.y - 10}" text-anchor="middle" font-size="10">${escapeXml(label)}</text>
6909
+ </g>`;
5797
6910
  }
5798
6911
  function renderOrbitLayer(scene, visibleObjectIds, includeStructures) {
5799
6912
  const backParts = [];
@@ -6376,6 +7489,13 @@ var WorldOrbit = (() => {
6376
7489
  value: `${details.object.resonance.targetObjectId} ${details.object.resonance.ratio}`
6377
7490
  });
6378
7491
  }
7492
+ if (details.relatedEvents.length > 0) {
7493
+ fields.set("events", {
7494
+ key: "events",
7495
+ label: "Events",
7496
+ value: details.relatedEvents.map((event) => event.event.label || event.event.id).join(", ")
7497
+ });
7498
+ }
6379
7499
  if (placement?.mode === "at") {
6380
7500
  fields.set("placement", {
6381
7501
  key: "placement",
@@ -6480,6 +7600,7 @@ var WorldOrbit = (() => {
6480
7600
  padding: options.padding,
6481
7601
  preset: options.preset,
6482
7602
  projection: options.projection,
7603
+ camera: options.camera ? { ...options.camera } : null,
6483
7604
  scaleModel: options.scaleModel ? { ...options.scaleModel } : void 0,
6484
7605
  theme: options.theme,
6485
7606
  layers: options.layers,
@@ -6768,6 +7889,11 @@ var WorldOrbit = (() => {
6768
7889
  if (currentInput.kind !== "scene" && viewpoint.projection !== scene.projection) {
6769
7890
  nextRenderOptions.projection = viewpoint.projection;
6770
7891
  }
7892
+ if (viewpoint.camera) {
7893
+ nextRenderOptions.camera = { ...viewpoint.camera };
7894
+ } else if (renderOptions.camera) {
7895
+ nextRenderOptions.camera = null;
7896
+ }
6771
7897
  if (viewpointLayers) {
6772
7898
  nextRenderOptions.layers = viewpointLayers;
6773
7899
  }
@@ -6790,6 +7916,12 @@ var WorldOrbit = (() => {
6790
7916
  emitAtlasStateChange();
6791
7917
  return true;
6792
7918
  },
7919
+ getActiveEventId() {
7920
+ return renderOptions.activeEventId ?? null;
7921
+ },
7922
+ setActiveEvent(id) {
7923
+ api.setRenderOptions({ activeEventId: id });
7924
+ },
6793
7925
  search(query, limit = 12) {
6794
7926
  return searchSceneObjects(scene, query, limit);
6795
7927
  },
@@ -7054,6 +8186,7 @@ var WorldOrbit = (() => {
7054
8186
  orbit: scene.orbitVisuals.find((orbit) => orbit.objectId === renderObject.objectId && !orbit.hidden) ?? null,
7055
8187
  relatedOrbits: scene.orbitVisuals.filter((orbit) => !orbit.hidden && (orbit.objectId === renderObject.objectId || renderObject.ancestorIds.includes(orbit.objectId) || renderObject.childIds.includes(orbit.objectId))),
7056
8188
  relations: scene.relations.filter((relation) => !relation.hidden && (relation.fromObjectId === renderObject.objectId || relation.toObjectId === renderObject.objectId)),
8189
+ relatedEvents: scene.events.filter((event) => !event.hidden && (event.targetObjectId === renderObject.objectId || event.objectIds.includes(renderObject.objectId))),
7057
8190
  parent: getObjectById(renderObject.parentId),
7058
8191
  children: renderObject.childIds.map((childId) => getObjectById(childId)).filter(Boolean),
7059
8192
  ancestors: renderObject.ancestorIds.map((ancestorId) => getObjectById(ancestorId)).filter(Boolean),
@@ -7367,16 +8500,19 @@ var WorldOrbit = (() => {
7367
8500
  function cloneRenderOptions(renderOptions) {
7368
8501
  return {
7369
8502
  ...renderOptions,
8503
+ camera: renderOptions.camera ? { ...renderOptions.camera } : null,
7370
8504
  filter: renderOptions.filter ? { ...renderOptions.filter } : void 0,
7371
8505
  scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
7372
8506
  layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
7373
- theme: renderOptions.theme && typeof renderOptions.theme === "object" ? { ...renderOptions.theme } : renderOptions.theme
8507
+ theme: renderOptions.theme && typeof renderOptions.theme === "object" ? { ...renderOptions.theme } : renderOptions.theme,
8508
+ activeEventId: renderOptions.activeEventId ?? null
7374
8509
  };
7375
8510
  }
7376
8511
  function mergeRenderOptions(current, next) {
7377
8512
  return {
7378
8513
  ...current,
7379
8514
  ...next,
8515
+ camera: next.camera !== void 0 ? next.camera ? { ...next.camera } : null : current.camera ? { ...current.camera } : null,
7380
8516
  filter: next.filter !== void 0 ? normalizeViewerFilter(next.filter) : current.filter ? { ...current.filter } : void 0,
7381
8517
  scaleModel: next.scaleModel ? {
7382
8518
  ...current.scaleModel ?? {},
@@ -7390,7 +8526,7 @@ var WorldOrbit = (() => {
7390
8526
  };
7391
8527
  }
7392
8528
  function hasSceneAffectingRenderOptions(options) {
7393
- 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;
8529
+ return options.width !== void 0 || options.height !== void 0 || options.padding !== void 0 || options.preset !== void 0 || options.projection !== void 0 || options.camera !== void 0 || options.scaleModel !== void 0 || options.activeEventId !== void 0;
7394
8530
  }
7395
8531
  function resolveSourceRenderOptions(loaded, renderOptions) {
7396
8532
  const atlasDocument = loaded.atlasDocument ?? loaded.draftDocument;
@@ -7701,7 +8837,11 @@ var WorldOrbit = (() => {
7701
8837
  var FIELD_HELP = {
7702
8838
  "defaults-view": {
7703
8839
  description: "Sets the default camera projection for the atlas.",
7704
- references: ["Topdown = map-like", "Isometric = angled overview"]
8840
+ references: [
8841
+ "Topdown = map-like",
8842
+ "Isometric = angled overview",
8843
+ "Orthographic/Perspective = 3D-ready semantic views"
8844
+ ]
7705
8845
  },
7706
8846
  "defaults-scale": {
7707
8847
  description: "Chooses the overall spacing/style preset used by the renderer.",
@@ -7713,15 +8853,71 @@ var WorldOrbit = (() => {
7713
8853
  },
7714
8854
  "viewpoint-projection": {
7715
8855
  description: "Overrides the projection for this saved viewpoint.",
7716
- references: ["Topdown = flat orbital map", "Isometric = angled scene"]
8856
+ references: [
8857
+ "Topdown = flat orbital map",
8858
+ "Isometric = angled scene",
8859
+ "Orthographic/Perspective = stored with current 2D fallback"
8860
+ ]
7717
8861
  },
7718
8862
  "viewpoint-zoom": {
7719
8863
  description: "Controls how closely this viewpoint frames the system.",
7720
8864
  references: ["1 = scene fit", "2+ = close-up"]
7721
8865
  },
7722
8866
  "viewpoint-rotation": {
7723
- description: "Rotates the saved camera angle in degrees.",
7724
- references: ["90deg = quarter turn", "180deg = flip"]
8867
+ description: "Legacy 2D screen rotation. This is separate from the Schema 2.5 camera block.",
8868
+ references: ["90deg = quarter turn", "Use camera.azimuth for semantic view direction"]
8869
+ },
8870
+ "viewpoint-camera-azimuth": {
8871
+ description: "Horizontal camera direction in degrees for Schema 2.5 viewpoints.",
8872
+ references: ["0 = forward/default", "90 = quarter orbit around the scene"]
8873
+ },
8874
+ "viewpoint-camera-elevation": {
8875
+ description: "Vertical camera tilt in degrees for 3D-ready viewpoints.",
8876
+ references: ["0 = level", "30 = gentle look down"]
8877
+ },
8878
+ "viewpoint-camera-roll": {
8879
+ description: "Rolls the camera around its forward axis.",
8880
+ references: ["0 = upright", "15 = slight bank"]
8881
+ },
8882
+ "viewpoint-camera-distance": {
8883
+ description: "Semantic camera distance for perspective viewpoints.",
8884
+ references: ["4 = close", "12 = wide framing"]
8885
+ },
8886
+ "viewpoint-events": {
8887
+ description: "Lists event IDs that this viewpoint should feature in its detail panel.",
8888
+ references: ["solar-eclipse-naar", "transit-window conjunction"]
8889
+ },
8890
+ "event-kind": {
8891
+ description: "Short semantic event type for tooling and viewer overlays.",
8892
+ references: ["solar-eclipse", "lunar-eclipse", "transit"]
8893
+ },
8894
+ "event-target": {
8895
+ description: "Primary object this event is centered on.",
8896
+ references: ["Naar", "Seyra"]
8897
+ },
8898
+ "event-participants": {
8899
+ description: "Objects that participate in the event snapshot or description.",
8900
+ references: ["Iyath Naar Seyra", "Naar Seyra Orun"]
8901
+ },
8902
+ "event-timing": {
8903
+ description: "Free-text timing note for the event.",
8904
+ references: ['"Every late bloom season"', '"At local midyear"']
8905
+ },
8906
+ "event-visibility": {
8907
+ description: "Notes where or how the event is visible.",
8908
+ references: ['"Visible from Naar"', '"Southern hemisphere only"']
8909
+ },
8910
+ "event-epoch": {
8911
+ description: "Optional event-wide epoch that event poses inherit unless they override it.",
8912
+ references: ['"JY-0001.0"', '"Naar bloom cycle year 18"']
8913
+ },
8914
+ "event-referencePlane": {
8915
+ description: "Optional event-wide reference plane for all poses in this snapshot.",
8916
+ references: ["ecliptic", "naar-equatorial"]
8917
+ },
8918
+ "event-viewpoints": {
8919
+ description: "Viewpoint IDs that should list this event prominently.",
8920
+ references: ["naar-system", "overview inner-system"]
7725
8921
  },
7726
8922
  "placement-target": {
7727
8923
  description: "Names the body or reference this object is attached to.",
@@ -7759,6 +8955,14 @@ var WorldOrbit = (() => {
7759
8955
  description: "Starting position of the object along its orbit.",
7760
8956
  references: ["0deg = start position", "180deg = opposite side"]
7761
8957
  },
8958
+ "pose-epoch": {
8959
+ description: "Overrides the effective epoch for this pose only.",
8960
+ references: ['"JY-0001.0"', "Falls back to event, object, then system"]
8961
+ },
8962
+ "pose-referencePlane": {
8963
+ description: "Overrides the effective reference plane for this pose only.",
8964
+ references: ["naar-equatorial", "Falls back to event, object, then system"]
8965
+ },
7762
8966
  "prop-radius": {
7763
8967
  description: "Visual body size or real-world-inspired radius value.",
7764
8968
  references: ["1re = Earth radius", "1sol = Sun radius"]
@@ -7841,12 +9045,23 @@ var WorldOrbit = (() => {
7841
9045
  minimap: true,
7842
9046
  tooltipMode: "hover",
7843
9047
  onSelectionChange(selectedObject) {
9048
+ const activeEventId = selection ? selectionEventId(selection) : null;
7844
9049
  if (ignoreViewerSelection || !selectedObject) {
7845
- if (!ignoreViewerSelection && selection?.kind === "object") {
7846
- setSelection(null, false, true);
9050
+ if (!ignoreViewerSelection) {
9051
+ if (selection?.kind === "event-pose" && selection.id) {
9052
+ setSelection({ kind: "event", id: selection.id }, false, true);
9053
+ } else if (selection?.kind === "object") {
9054
+ setSelection(activeEventId ? { kind: "event", id: activeEventId } : null, false, true);
9055
+ } else if (selection?.kind === "event" && selection.id) {
9056
+ setSelection({ kind: "event", id: selection.id }, false, true);
9057
+ }
7847
9058
  }
7848
9059
  return;
7849
9060
  }
9061
+ if (activeEventId && findEventPose2(atlasDocument, activeEventId, selectedObject.objectId)) {
9062
+ setSelection({ kind: "event-pose", id: activeEventId, key: selectedObject.objectId }, false, true);
9063
+ return;
9064
+ }
7850
9065
  setSelection({ kind: "object", id: selectedObject.objectId }, false, true);
7851
9066
  },
7852
9067
  onViewChange() {
@@ -7856,6 +9071,7 @@ var WorldOrbit = (() => {
7856
9071
  toolbar.addEventListener("click", handleToolbarClick);
7857
9072
  outline.addEventListener("click", handleOutlineClick);
7858
9073
  overlay.addEventListener("pointerdown", handleOverlayPointerDown);
9074
+ inspector?.addEventListener("click", handleInspectorClick);
7859
9075
  inspector?.addEventListener("input", handleInspectorInput);
7860
9076
  inspector?.addEventListener("change", handleInspectorChange);
7861
9077
  sourcePane?.addEventListener("input", handleSourceInput);
@@ -7930,6 +9146,30 @@ var WorldOrbit = (() => {
7930
9146
  replaceAtlasDocument(nextDocument, true, { kind: "object", id });
7931
9147
  return id;
7932
9148
  },
9149
+ addEvent() {
9150
+ const id = createUniqueId("event", atlasDocument.events.map((event) => event.id));
9151
+ const created = {
9152
+ id,
9153
+ kind: "",
9154
+ label: humanizeIdentifier4(id),
9155
+ summary: null,
9156
+ targetObjectId: null,
9157
+ participantObjectIds: [],
9158
+ timing: null,
9159
+ visibility: null,
9160
+ epoch: null,
9161
+ referencePlane: null,
9162
+ tags: [],
9163
+ color: null,
9164
+ hidden: false,
9165
+ positions: []
9166
+ };
9167
+ const nextDocument = cloneAtlasDocument(atlasDocument);
9168
+ nextDocument.events.push(created);
9169
+ nextDocument.events.sort(compareEvents);
9170
+ replaceAtlasDocument(nextDocument, true, { kind: "event", id });
9171
+ return id;
9172
+ },
7933
9173
  addViewpoint() {
7934
9174
  const id = createUniqueId("viewpoint", atlasDocument.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []);
7935
9175
  const created = {
@@ -7938,10 +9178,12 @@ var WorldOrbit = (() => {
7938
9178
  summary: "",
7939
9179
  focusObjectId: null,
7940
9180
  selectedObjectId: null,
9181
+ events: [],
7941
9182
  projection: atlasDocument.system?.defaults.view ?? "topdown",
7942
9183
  preset: atlasDocument.system?.defaults.preset ?? null,
7943
9184
  zoom: null,
7944
9185
  rotationDeg: 0,
9186
+ camera: null,
7945
9187
  layers: {},
7946
9188
  filter: null
7947
9189
  };
@@ -7998,6 +9240,7 @@ var WorldOrbit = (() => {
7998
9240
  toolbar.removeEventListener("click", handleToolbarClick);
7999
9241
  outline.removeEventListener("click", handleOutlineClick);
8000
9242
  overlay.removeEventListener("pointerdown", handleOverlayPointerDown);
9243
+ inspector?.removeEventListener("click", handleInspectorClick);
8001
9244
  inspector?.removeEventListener("input", handleInspectorInput);
8002
9245
  inspector?.removeEventListener("change", handleInspectorChange);
8003
9246
  sourcePane?.removeEventListener("input", handleSourceInput);
@@ -8043,7 +9286,7 @@ var WorldOrbit = (() => {
8043
9286
  }
8044
9287
  function getCurrentSourceForExport() {
8045
9288
  if (dragState?.changed) {
8046
- return formatDocument(atlasDocument, { schema: "2.0" });
9289
+ return formatAtlasSource(atlasDocument);
8047
9290
  }
8048
9291
  return canonicalSource;
8049
9292
  }
@@ -8082,7 +9325,7 @@ var WorldOrbit = (() => {
8082
9325
  }
8083
9326
  clearSourceInputTimer();
8084
9327
  atlasDocument = cloneAtlasDocument(nextDocument);
8085
- canonicalSource = formatDocument(atlasDocument, { schema: "2.0" });
9328
+ canonicalSource = formatAtlasSource(atlasDocument);
8086
9329
  if (!preserveSourceText) {
8087
9330
  sourceText = canonicalSource;
8088
9331
  }
@@ -8123,10 +9366,10 @@ var WorldOrbit = (() => {
8123
9366
  if (commitHistory) {
8124
9367
  history.push(createHistoryEntry());
8125
9368
  future.length = 0;
8126
- sourceText = formatDocument(nextDocument, { schema: "2.0" });
9369
+ sourceText = formatAtlasSource(nextDocument);
8127
9370
  }
8128
9371
  atlasDocument = cloneAtlasDocument(nextDocument);
8129
- canonicalSource = formatDocument(atlasDocument, { schema: "2.0" });
9372
+ canonicalSource = formatAtlasSource(atlasDocument);
8130
9373
  diagnostics = mergeDiagnostics(loadedDiagnostics, collectDocumentDiagnostics(atlasDocument));
8131
9374
  selection = normalizeSelection(selection);
8132
9375
  syncViewer({
@@ -8145,11 +9388,13 @@ var WorldOrbit = (() => {
8145
9388
  const previousState = viewer.getState();
8146
9389
  const currentRenderOptions = viewer.getRenderOptions();
8147
9390
  const nextPreset = atlasDocument.system?.defaults.preset ?? "atlas-card";
9391
+ const nextActiveEventId = selection ? selectionEventId(selection) : null;
8148
9392
  ignoreViewerSelection = true;
8149
- if (currentRenderOptions.preset !== nextPreset || currentRenderOptions.projection !== "document") {
9393
+ if (currentRenderOptions.preset !== nextPreset || currentRenderOptions.projection !== "document" || (currentRenderOptions.activeEventId ?? null) !== nextActiveEventId) {
8150
9394
  viewer.setRenderOptions({
8151
9395
  preset: nextPreset,
8152
- projection: "document"
9396
+ projection: "document",
9397
+ activeEventId: nextActiveEventId
8153
9398
  });
8154
9399
  }
8155
9400
  viewer.setDocument(materializeAtlasDocument(atlasDocument));
@@ -8158,10 +9403,12 @@ var WorldOrbit = (() => {
8158
9403
  } else if (options2.preserveCamera !== false) {
8159
9404
  viewer.setState({
8160
9405
  ...previousState,
8161
- selectedObjectId: selection?.kind === "object" ? selection.id ?? null : null
9406
+ selectedObjectId: selection?.kind === "object" ? selection.id ?? null : selection?.kind === "event-pose" ? selection.key ?? null : null
8162
9407
  });
8163
9408
  } else if (selection?.kind === "object" && selection.id) {
8164
9409
  viewer.focusObject(selection.id);
9410
+ } else if (selection?.kind === "event-pose" && selection.key) {
9411
+ viewer.focusObject(selection.key);
8165
9412
  }
8166
9413
  ignoreViewerSelection = false;
8167
9414
  }
@@ -8180,8 +9427,11 @@ var WorldOrbit = (() => {
8180
9427
  selection = normalizeSelection(nextSelection);
8181
9428
  if (syncViewerSelection) {
8182
9429
  ignoreViewerSelection = true;
9430
+ viewer.setRenderOptions({ activeEventId: selection ? selectionEventId(selection) : null });
8183
9431
  if (selection?.kind === "object" && selection.id) {
8184
9432
  viewer.focusObject(selection.id);
9433
+ } else if (selection?.kind === "event-pose" && selection.key) {
9434
+ viewer.focusObject(selection.key);
8185
9435
  } else if (selection?.kind === "viewpoint" && selection.id) {
8186
9436
  viewer.goToViewpoint(selection.id);
8187
9437
  }
@@ -8231,6 +9481,7 @@ var WorldOrbit = (() => {
8231
9481
  ${OBJECT_TYPES.map((type) => `<option value="${escapeHtml3(type)}"${type === objectType ? " selected" : ""}>${escapeHtml3(humanizeIdentifier4(type))}</option>`).join("")}
8232
9482
  </select>
8233
9483
  <button type="button" data-editor-action="add-object">Add object</button>
9484
+ <button type="button" data-editor-action="add-event">Add event</button>
8234
9485
  <button type="button" data-editor-action="add-viewpoint">Add viewpoint</button>
8235
9486
  <button type="button" data-editor-action="add-annotation">Add annotation</button>
8236
9487
  <button type="button" data-editor-action="add-metadata">Add metadata</button>
@@ -8265,6 +9516,10 @@ var WorldOrbit = (() => {
8265
9516
  <h3>Annotations</h3>
8266
9517
  ${(atlasDocument.system?.annotations.length ?? 0) > 0 ? atlasDocument.system?.annotations.map((annotation) => renderOutlineButton({ kind: "annotation", id: annotation.id }, annotation.label, activeKey, diagnosticBuckets)).join("") : `<p class="wo-editor-empty">No annotations yet.</p>`}
8267
9518
  </div>
9519
+ <div class="wo-editor-outline-section">
9520
+ <h3>Events</h3>
9521
+ ${atlasDocument.events.length > 0 ? atlasDocument.events.map((eventEntry) => renderEventOutlineItems(eventEntry, activeKey, diagnosticBuckets)).join("") : `<p class="wo-editor-empty">No events yet.</p>`}
9522
+ </div>
8268
9523
  <div class="wo-editor-outline-section">
8269
9524
  <h3>Objects</h3>
8270
9525
  ${atlasDocument.objects.length > 0 ? atlasDocument.objects.map((object) => renderOutlineButton({ kind: "object", id: object.id }, `${object.id} - ${object.type}`, activeKey, diagnosticBuckets)).join("") : `<p class="wo-editor-empty">No objects yet.</p>`}
@@ -8302,6 +9557,7 @@ var WorldOrbit = (() => {
8302
9557
  selection: selection ? { path: { ...selection } } : null,
8303
9558
  system: atlasDocument.system,
8304
9559
  viewpoints: atlasDocument.system?.viewpoints ?? [],
9560
+ events: atlasDocument.events,
8305
9561
  objects: atlasDocument.objects
8306
9562
  };
8307
9563
  if (!selection) {
@@ -8330,6 +9586,16 @@ var WorldOrbit = (() => {
8330
9586
  applyInspectorSectionState(inspector, inspectorSectionState);
8331
9587
  decorateInspectorDiagnostics(selection, diagnostics);
8332
9588
  return;
9589
+ case "event":
9590
+ inspector.innerHTML = diagnosticSummary + renderEventInspector(formState, selection.id ?? "");
9591
+ applyInspectorSectionState(inspector, inspectorSectionState);
9592
+ decorateInspectorDiagnostics(selection, diagnostics);
9593
+ return;
9594
+ case "event-pose":
9595
+ inspector.innerHTML = diagnosticSummary + renderEventPoseInspector(formState, selection.id ?? "", selection.key ?? "");
9596
+ applyInspectorSectionState(inspector, inspectorSectionState);
9597
+ decorateInspectorDiagnostics(selection, diagnostics);
9598
+ return;
8333
9599
  case "annotation":
8334
9600
  inspector.innerHTML = diagnosticSummary + renderAnnotationInspector(formState, selection.id ?? "");
8335
9601
  applyInspectorSectionState(inspector, inspectorSectionState);
@@ -8362,10 +9628,11 @@ var WorldOrbit = (() => {
8362
9628
  return;
8363
9629
  }
8364
9630
  overlay.innerHTML = "";
8365
- if (selection?.kind !== "object" || !selection.id) {
9631
+ const selectedObjectId = selection?.kind === "object" ? selection.id ?? null : selection?.kind === "event-pose" ? selection.key ?? null : null;
9632
+ if (!selectedObjectId) {
8366
9633
  return;
8367
9634
  }
8368
- const details = viewer.getObjectDetails(selection.id);
9635
+ const details = viewer.getObjectDetails(selectedObjectId);
8369
9636
  if (!details) {
8370
9637
  return;
8371
9638
  }
@@ -8477,6 +9744,9 @@ var WorldOrbit = (() => {
8477
9744
  case "add-viewpoint":
8478
9745
  api.addViewpoint();
8479
9746
  return;
9747
+ case "add-event":
9748
+ api.addEvent();
9749
+ return;
8480
9750
  case "add-annotation":
8481
9751
  api.addAnnotation();
8482
9752
  return;
@@ -8511,6 +9781,32 @@ var WorldOrbit = (() => {
8511
9781
  key: button.dataset.pathKey || void 0
8512
9782
  }, true, true);
8513
9783
  }
9784
+ function handleInspectorClick(event) {
9785
+ const pathButton = event.target?.closest("[data-path-kind]");
9786
+ if (pathButton) {
9787
+ setSelection({
9788
+ kind: pathButton.dataset.pathKind,
9789
+ id: pathButton.dataset.pathId || void 0,
9790
+ key: pathButton.dataset.pathKey || void 0
9791
+ }, true, true);
9792
+ return;
9793
+ }
9794
+ const actionButton = event.target?.closest("[data-editor-action]");
9795
+ if (!actionButton) {
9796
+ return;
9797
+ }
9798
+ if (actionButton.dataset.editorAction === "add-event-pose") {
9799
+ const eventId = actionButton.dataset.editorEventId || (selection?.kind === "event" || selection?.kind === "event-pose" ? selection.id ?? "" : "");
9800
+ if (!eventId) {
9801
+ return;
9802
+ }
9803
+ const nextDocument = addEventPose(atlasDocument, eventId);
9804
+ const createdEvent = nextDocument.events.find((entry) => entry.id === eventId);
9805
+ const createdPose = createdEvent?.positions.at(-1) ?? createdEvent?.positions[0];
9806
+ replaceAtlasDocument(nextDocument, true, createdPose ? { kind: "event-pose", id: eventId, key: createdPose.objectId } : { kind: "event", id: eventId });
9807
+ return;
9808
+ }
9809
+ }
8514
9810
  function handleInspectorInput() {
8515
9811
  applyInspectorState(false);
8516
9812
  }
@@ -8534,6 +9830,12 @@ var WorldOrbit = (() => {
8534
9830
  case "viewpoint":
8535
9831
  replaceAtlasDocument(buildViewpointDocumentFromInspector(selection.id ?? ""), commitHistory, selection, false);
8536
9832
  return;
9833
+ case "event":
9834
+ replaceAtlasDocument(buildEventDocumentFromInspector(selection.id ?? ""), commitHistory, selection, false);
9835
+ return;
9836
+ case "event-pose":
9837
+ replaceAtlasDocument(buildEventPoseDocumentFromInspector(selection.id ?? "", selection.key ?? ""), commitHistory, selection, false);
9838
+ return;
8537
9839
  case "annotation":
8538
9840
  replaceAtlasDocument(buildAnnotationDocumentFromInspector(selection.id ?? ""), commitHistory, selection, false);
8539
9841
  return;
@@ -8602,6 +9904,7 @@ var WorldOrbit = (() => {
8602
9904
  kind,
8603
9905
  objectId,
8604
9906
  pointerId: event.pointerId,
9907
+ path: selection ? { ...selection } : { kind: "object", id: objectId },
8605
9908
  startedFrom: createHistoryEntry(),
8606
9909
  changed: false,
8607
9910
  orbitRadiusContext: kind === "orbit-radius" && details ? createOrbitRadiusDragContext(atlasDocument, viewer.getScene(), details) : null
@@ -8610,7 +9913,7 @@ var WorldOrbit = (() => {
8610
9913
  event.preventDefault();
8611
9914
  }
8612
9915
  function handleWindowPointerMove(event) {
8613
- if (!dragState || dragState.pointerId !== event.pointerId || selection?.kind !== "object" || selection.id !== dragState.objectId) {
9916
+ if (!dragState || dragState.pointerId !== event.pointerId || !selection || selectionKey(selection) !== selectionKey(dragState.path)) {
8614
9917
  return;
8615
9918
  }
8616
9919
  const details = viewer.getObjectDetails(dragState.objectId);
@@ -8622,27 +9925,27 @@ var WorldOrbit = (() => {
8622
9925
  switch (dragState.kind) {
8623
9926
  case "orbit-phase":
8624
9927
  if (details.object.placement?.mode === "orbit" && details.orbit) {
8625
- nextDocument = updateOrbitPhase(atlasDocument, dragState.objectId, details, pointer);
9928
+ nextDocument = updateOrbitPhase(atlasDocument, dragState.path, dragState.objectId, details, pointer);
8626
9929
  }
8627
9930
  break;
8628
9931
  case "orbit-radius":
8629
9932
  if (details.object.placement?.mode === "orbit" && details.orbit) {
8630
- nextDocument = updateOrbitRadius(atlasDocument, dragState.objectId, details, pointer, dragState.orbitRadiusContext ?? null);
9933
+ nextDocument = updateOrbitRadius(atlasDocument, dragState.path, dragState.objectId, details, pointer, dragState.orbitRadiusContext ?? null);
8631
9934
  }
8632
9935
  break;
8633
9936
  case "at-reference":
8634
9937
  if (details.object.placement?.mode === "at") {
8635
- nextDocument = updateAtReference(atlasDocument, dragState.objectId, viewer.getScene(), pointer);
9938
+ nextDocument = updateAtReference(atlasDocument, dragState.path, dragState.objectId, viewer.getScene(), pointer);
8636
9939
  }
8637
9940
  break;
8638
9941
  case "surface-target":
8639
9942
  if (details.object.placement?.mode === "surface") {
8640
- nextDocument = updateSurfaceTarget(atlasDocument, dragState.objectId, viewer.getScene(), pointer);
9943
+ nextDocument = updateSurfaceTarget(atlasDocument, dragState.path, dragState.objectId, viewer.getScene(), pointer);
8641
9944
  }
8642
9945
  break;
8643
9946
  case "free-distance":
8644
9947
  if (details.object.placement?.mode === "free") {
8645
- nextDocument = updateFreeDistance(atlasDocument, dragState.objectId, viewer.getScene(), details, pointer);
9948
+ nextDocument = updateFreeDistance(atlasDocument, dragState.path, dragState.objectId, viewer.getScene(), details, pointer);
8646
9949
  }
8647
9950
  break;
8648
9951
  }
@@ -8674,7 +9977,7 @@ var WorldOrbit = (() => {
8674
9977
  }
8675
9978
  history.push(dragState.startedFrom);
8676
9979
  future.length = 0;
8677
- canonicalSource = formatDocument(atlasDocument, { schema: "2.0" });
9980
+ canonicalSource = formatAtlasSource(atlasDocument);
8678
9981
  sourceText = canonicalSource;
8679
9982
  dragState = null;
8680
9983
  renderAll();
@@ -8772,15 +10075,18 @@ var WorldOrbit = (() => {
8772
10075
  preset: readOptionalTextInput(form, "viewpoint-preset") ?? null,
8773
10076
  zoom: parseNullableNumber(readOptionalTextInput(form, "viewpoint-zoom")),
8774
10077
  rotationDeg: parseNullableNumber(readOptionalTextInput(form, "viewpoint-rotation")) ?? 0,
10078
+ camera: buildViewCameraFromForm(form),
8775
10079
  layers: {
8776
10080
  background: readCheckbox(form, "layer-background"),
8777
10081
  guides: readCheckbox(form, "layer-guides"),
8778
10082
  "orbits-back": readCheckbox(form, "layer-orbits-back"),
8779
10083
  "orbits-front": readCheckbox(form, "layer-orbits-front"),
10084
+ events: readCheckbox(form, "layer-events"),
8780
10085
  objects: readCheckbox(form, "layer-objects"),
8781
10086
  labels: readCheckbox(form, "layer-labels"),
8782
10087
  metadata: readCheckbox(form, "layer-metadata")
8783
10088
  },
10089
+ events: splitTokens(readOptionalTextInput(form, "viewpoint-events")),
8784
10090
  filter: {
8785
10091
  query: readOptionalTextInput(form, "filter-query"),
8786
10092
  objectTypes: parseObjectTypes(readOptionalTextInput(form, "filter-object-types")),
@@ -8794,6 +10100,70 @@ var WorldOrbit = (() => {
8794
10100
  }
8795
10101
  return nextDocument;
8796
10102
  }
10103
+ function buildEventDocumentFromInspector(currentId) {
10104
+ const nextDocument = cloneAtlasDocument(atlasDocument);
10105
+ const form = inspector?.querySelector("form[data-editor-form='event']");
10106
+ const current = nextDocument.events.find((entry) => entry.id === currentId);
10107
+ if (!form || !current) {
10108
+ return nextDocument;
10109
+ }
10110
+ const nextId = readTextInput(form, "event-id") || current.id;
10111
+ const replacement = {
10112
+ ...current,
10113
+ id: nextId,
10114
+ kind: readTextInput(form, "event-kind"),
10115
+ label: readTextInput(form, "event-label") || current.label,
10116
+ summary: readOptionalTextInput(form, "event-summary"),
10117
+ targetObjectId: readOptionalTextInput(form, "event-target"),
10118
+ participantObjectIds: splitTokens(readOptionalTextInput(form, "event-participants")),
10119
+ timing: readOptionalTextInput(form, "event-timing"),
10120
+ visibility: readOptionalTextInput(form, "event-visibility"),
10121
+ epoch: readOptionalTextInput(form, "event-epoch"),
10122
+ referencePlane: readOptionalTextInput(form, "event-referencePlane"),
10123
+ tags: splitTokens(readOptionalTextInput(form, "event-tags")),
10124
+ color: readOptionalTextInput(form, "event-color"),
10125
+ hidden: readCheckbox(form, "event-hidden")
10126
+ };
10127
+ nextDocument.events = nextDocument.events.filter((entry) => entry.id !== current.id).concat(replacement).sort(compareEvents);
10128
+ syncEventViewpointReferences(nextDocument, current.id, replacement.id, splitTokens(readOptionalTextInput(form, "event-viewpoints")));
10129
+ if (current.id !== replacement.id) {
10130
+ selection = { kind: "event", id: replacement.id };
10131
+ }
10132
+ return nextDocument;
10133
+ }
10134
+ function buildEventPoseDocumentFromInspector(eventId, objectId) {
10135
+ const nextDocument = cloneAtlasDocument(atlasDocument);
10136
+ const form = inspector?.querySelector("form[data-editor-form='event-pose']");
10137
+ const eventEntry = nextDocument.events.find((entry) => entry.id === eventId);
10138
+ const currentPose = eventEntry?.positions.find((entry) => entry.objectId === objectId);
10139
+ if (!form || !eventEntry || !currentPose) {
10140
+ return nextDocument;
10141
+ }
10142
+ const nextObjectId = readTextInput(form, "pose-object-id") || currentPose.objectId;
10143
+ const replacement = {
10144
+ objectId: nextObjectId,
10145
+ placement: buildPlacementFromPoseForm(form, currentPose),
10146
+ epoch: readOptionalTextInput(form, "pose-epoch"),
10147
+ referencePlane: readOptionalTextInput(form, "pose-referencePlane")
10148
+ };
10149
+ const inner = parseOptionalUnit(readOptionalTextInput(form, "prop-inner"));
10150
+ const outer = parseOptionalUnit(readOptionalTextInput(form, "prop-outer"));
10151
+ if (inner) {
10152
+ replacement.inner = inner;
10153
+ }
10154
+ if (outer) {
10155
+ replacement.outer = outer;
10156
+ }
10157
+ eventEntry.positions = eventEntry.positions.filter((entry) => entry.objectId !== currentPose.objectId).concat(replacement).sort(compareEventPoses);
10158
+ if (eventEntry.targetObjectId !== replacement.objectId && !eventEntry.participantObjectIds.includes(replacement.objectId)) {
10159
+ eventEntry.participantObjectIds.push(replacement.objectId);
10160
+ eventEntry.participantObjectIds.sort((left, right) => left.localeCompare(right));
10161
+ }
10162
+ if (currentPose.objectId !== replacement.objectId) {
10163
+ selection = { kind: "event-pose", id: eventId, key: replacement.objectId };
10164
+ }
10165
+ return nextDocument;
10166
+ }
8797
10167
  function buildAnnotationDocumentFromInspector(currentId) {
8798
10168
  const nextDocument = cloneAtlasDocument(atlasDocument);
8799
10169
  const form = inspector?.querySelector("form[data-editor-form='annotation']");
@@ -8986,7 +10356,7 @@ var WorldOrbit = (() => {
8986
10356
  const atlasDocument2 = cloneAtlasDocument(options.atlasDocument);
8987
10357
  return {
8988
10358
  atlasDocument: atlasDocument2,
8989
- source: formatDocument(atlasDocument2, { schema: "2.0" }),
10359
+ source: formatAtlasSource(atlasDocument2),
8990
10360
  diagnostics: collectDocumentDiagnostics(atlasDocument2)
8991
10361
  };
8992
10362
  }
@@ -8996,7 +10366,7 @@ var WorldOrbit = (() => {
8996
10366
  const atlasDocument2 = loaded.value.atlasDocument ?? upgradeDocumentToV2(loaded.value.document);
8997
10367
  return {
8998
10368
  atlasDocument: atlasDocument2,
8999
- source: formatDocument(atlasDocument2, { schema: "2.0" }),
10369
+ source: formatAtlasSource(atlasDocument2),
9000
10370
  diagnostics: mergeDiagnostics(resolveAtlasDiagnostics(atlasDocument2, loaded.diagnostics), collectDocumentDiagnostics(atlasDocument2))
9001
10371
  };
9002
10372
  }
@@ -9004,10 +10374,13 @@ var WorldOrbit = (() => {
9004
10374
  const atlasDocument = createEmptyAtlasDocument("WorldOrbit");
9005
10375
  return {
9006
10376
  atlasDocument,
9007
- source: formatDocument(atlasDocument, { schema: "2.0" }),
10377
+ source: formatAtlasSource(atlasDocument),
9008
10378
  diagnostics: collectDocumentDiagnostics(atlasDocument)
9009
10379
  };
9010
10380
  }
10381
+ function formatAtlasSource(document2) {
10382
+ return formatDocument(document2, { schema: document2.version });
10383
+ }
9011
10384
  function buildEditorMarkup() {
9012
10385
  const previewOpen = shouldPreviewSectionBeOpenByDefault();
9013
10386
  return `<section class="wo-editor-shell">
@@ -9122,6 +10495,12 @@ var WorldOrbit = (() => {
9122
10495
  const badge = bucket && (bucket.errors > 0 || bucket.warnings > 0) ? `<span class="wo-editor-outline-badge${bucket.errors > 0 ? " is-error" : " is-warning"}">${bucket.errors > 0 ? bucket.errors : bucket.warnings}</span>` : "";
9123
10496
  return `<button type="button" class="wo-editor-outline-item${key === activeKey ? " is-active" : ""}" data-path-kind="${escapeHtml3(path.kind)}"${path.id ? ` data-path-id="${escapeHtml3(path.id)}"` : ""}${path.key ? ` data-path-key="${escapeHtml3(path.key)}"` : ""}><span>${escapeHtml3(label)}</span>${badge}</button>`;
9124
10497
  }
10498
+ function renderEventOutlineItems(eventEntry, activeKey, diagnosticBuckets) {
10499
+ return `<div class="wo-editor-outline-group">
10500
+ ${renderOutlineButton({ kind: "event", id: eventEntry.id }, eventEntry.label || eventEntry.id, activeKey, diagnosticBuckets)}
10501
+ ${eventEntry.positions.length > 0 ? `<div class="wo-editor-outline-children">${[...eventEntry.positions].sort(compareEventPoses).map((pose) => renderOutlineButton({ kind: "event-pose", id: eventEntry.id, key: pose.objectId }, pose.objectId, activeKey, diagnosticBuckets)).join("")}</div>` : ""}
10502
+ </div>`;
10503
+ }
9125
10504
  function renderSystemInspector(formState) {
9126
10505
  return `<form class="wo-editor-form" data-editor-form="system">
9127
10506
  <h2>System</h2>
@@ -9135,7 +10514,9 @@ var WorldOrbit = (() => {
9135
10514
  <h2>Defaults</h2>
9136
10515
  ${renderInspectorSection("defaults", "basics", "Basics", `${renderSelectField("Projection", "defaults-view", [
9137
10516
  ["topdown", "Topdown"],
9138
- ["isometric", "Isometric"]
10517
+ ["isometric", "Isometric"],
10518
+ ["orthographic", "Orthographic"],
10519
+ ["perspective", "Perspective"]
9139
10520
  ], defaults?.view ?? "topdown")}
9140
10521
  ${renderTextField("Scale preset", "defaults-scale", defaults?.scale ?? "")}
9141
10522
  ${renderTextField("Units", "defaults-units", defaults?.units ?? "")}
@@ -9171,7 +10552,9 @@ var WorldOrbit = (() => {
9171
10552
  ${renderTextField("Selected object", "viewpoint-select", viewpoint.selectedObjectId ?? "")}
9172
10553
  ${renderSelectField("Projection", "viewpoint-projection", [
9173
10554
  ["topdown", "Topdown"],
9174
- ["isometric", "Isometric"]
10555
+ ["isometric", "Isometric"],
10556
+ ["orthographic", "Orthographic"],
10557
+ ["perspective", "Perspective"]
9175
10558
  ], viewpoint.projection)}
9176
10559
  ${renderSelectField("Preset", "viewpoint-preset", [
9177
10560
  ["", "Document default"],
@@ -9182,12 +10565,18 @@ var WorldOrbit = (() => {
9182
10565
  ], viewpoint.preset ?? "")}
9183
10566
  ${renderTextField("Zoom", "viewpoint-zoom", viewpoint.zoom === null ? "" : String(viewpoint.zoom))}
9184
10567
  ${renderTextField("Rotation", "viewpoint-rotation", String(viewpoint.rotationDeg))}`, true)}
10568
+ ${renderInspectorSection("viewpoint", "camera", "Camera", `${renderTextField("Azimuth", "viewpoint-camera-azimuth", viewpoint.camera?.azimuth === null || viewpoint.camera?.azimuth === void 0 ? "" : String(viewpoint.camera.azimuth))}
10569
+ ${renderTextField("Elevation", "viewpoint-camera-elevation", viewpoint.camera?.elevation === null || viewpoint.camera?.elevation === void 0 ? "" : String(viewpoint.camera.elevation))}
10570
+ ${renderTextField("Roll", "viewpoint-camera-roll", viewpoint.camera?.roll === null || viewpoint.camera?.roll === void 0 ? "" : String(viewpoint.camera.roll))}
10571
+ ${renderTextField("Distance", "viewpoint-camera-distance", viewpoint.camera?.distance === null || viewpoint.camera?.distance === void 0 ? "" : String(viewpoint.camera.distance))}
10572
+ <p class="wo-editor-inline-note">Rotation stays a 2D screen-rotation hint. The camera block stores Schema 2.5 view direction and framing.</p>`)}
9185
10573
  ${renderInspectorSection("viewpoint", "layers", "Layers", `<fieldset class="wo-editor-fieldset">
9186
10574
  <legend>Layers</legend>
9187
10575
  ${renderCheckboxField("Background", "layer-background", viewpoint.layers.background !== false)}
9188
10576
  ${renderCheckboxField("Guides", "layer-guides", viewpoint.layers.guides !== false)}
9189
10577
  ${renderCheckboxField("Orbits back", "layer-orbits-back", viewpoint.layers["orbits-back"] !== false)}
9190
10578
  ${renderCheckboxField("Orbits front", "layer-orbits-front", viewpoint.layers["orbits-front"] !== false)}
10579
+ ${renderCheckboxField("Events", "layer-events", viewpoint.layers.events !== false)}
9191
10580
  ${renderCheckboxField("Objects", "layer-objects", viewpoint.layers.objects !== false)}
9192
10581
  ${renderCheckboxField("Labels", "layer-labels", viewpoint.layers.labels !== false)}
9193
10582
  ${renderCheckboxField("Metadata", "layer-metadata", viewpoint.layers.metadata !== false)}
@@ -9195,7 +10584,75 @@ var WorldOrbit = (() => {
9195
10584
  ${renderInspectorSection("viewpoint", "filter", "Filter", `${renderTextField("Filter query", "filter-query", viewpoint.filter?.query ?? "")}
9196
10585
  ${renderTextField("Filter object types", "filter-object-types", viewpoint.filter?.objectTypes.join(" ") ?? "")}
9197
10586
  ${renderTextField("Filter tags", "filter-tags", viewpoint.filter?.tags.join(" ") ?? "")}
9198
- ${renderTextField("Filter groups", "filter-groups", viewpoint.filter?.groupIds.join(" ") ?? "")}`)}
10587
+ ${renderTextField("Filter groups", "filter-groups", viewpoint.filter?.groupIds.join(" ") ?? "")}
10588
+ ${renderTextField("Events", "viewpoint-events", viewpoint.events.join(" "))}`)}
10589
+ </form>`;
10590
+ }
10591
+ function renderEventInspector(formState, id) {
10592
+ const eventEntry = formState.events.find((entry) => entry.id === id);
10593
+ if (!eventEntry) {
10594
+ return `<p class="wo-editor-empty">Event not found.</p>`;
10595
+ }
10596
+ const linkedViewpoints = formState.viewpoints.filter((viewpoint) => viewpoint.events.includes(eventEntry.id)).map((viewpoint) => viewpoint.id).join(" ");
10597
+ return `<form class="wo-editor-form" data-editor-form="event">
10598
+ <h2>Event</h2>
10599
+ ${renderInspectorSection("event", "basics", "Basics", `${renderTextField("ID", "event-id", eventEntry.id)}
10600
+ ${renderTextField("Kind", "event-kind", eventEntry.kind)}
10601
+ ${renderTextField("Label", "event-label", eventEntry.label)}
10602
+ ${renderTextAreaField("Summary", "event-summary", eventEntry.summary ?? "")}
10603
+ ${renderTextField("Target object", "event-target", eventEntry.targetObjectId ?? "")}
10604
+ ${renderTextField("Participants", "event-participants", eventEntry.participantObjectIds.join(" "))}
10605
+ ${renderTextField("Timing", "event-timing", eventEntry.timing ?? "")}
10606
+ ${renderTextField("Visibility", "event-visibility", eventEntry.visibility ?? "")}
10607
+ ${renderTextField("Epoch", "event-epoch", eventEntry.epoch ?? "")}
10608
+ ${renderTextField("Reference plane", "event-referencePlane", eventEntry.referencePlane ?? "")}
10609
+ ${renderTextField("Tags", "event-tags", eventEntry.tags.join(" "))}
10610
+ ${renderTextField("Color", "event-color", eventEntry.color ?? "")}
10611
+ ${renderCheckboxField("Hidden", "event-hidden", eventEntry.hidden === true)}`, true)}
10612
+ ${renderInspectorSection("event", "viewpoints", "Viewpoints", `${renderTextField("Viewpoints", "event-viewpoints", linkedViewpoints)}`)}
10613
+ ${renderInspectorSection("event", "positions", "Positions", `${eventEntry.positions.length > 0 ? `<div class="wo-editor-inline-list">${eventEntry.positions.map((pose) => renderOutlineButton({ kind: "event-pose", id: eventEntry.id, key: pose.objectId }, pose.objectId, null, /* @__PURE__ */ new Map())).join("")}</div>` : `<p class="wo-editor-empty">No event poses yet.</p>`}
10614
+ <div class="wo-editor-inline-actions">
10615
+ <button type="button" data-editor-action="add-event-pose" data-editor-event-id="${escapeHtml3(eventEntry.id)}">Add pose</button>
10616
+ </div>`)}
10617
+ </form>`;
10618
+ }
10619
+ function renderEventPoseInspector(formState, eventId, objectId) {
10620
+ const eventEntry = formState.events.find((entry) => entry.id === eventId);
10621
+ const pose = eventEntry?.positions.find((entry) => entry.objectId === objectId);
10622
+ if (!eventEntry || !pose) {
10623
+ return `<p class="wo-editor-empty">Event pose not found.</p>`;
10624
+ }
10625
+ const placementMode = pose.placement?.mode ?? "";
10626
+ const placementTarget = pose.placement?.mode === "orbit" || pose.placement?.mode === "surface" || pose.placement?.mode === "at" ? pose.placement.target : "";
10627
+ const freeValue = pose.placement?.mode === "free" ? pose.placement.distance ? formatUnitValue3(pose.placement.distance) : pose.placement.descriptor ?? "" : "";
10628
+ return `<form class="wo-editor-form" data-editor-form="event-pose">
10629
+ <h2>Event Pose</h2>
10630
+ <p class="wo-editor-inline-note">Event <strong>${escapeHtml3(eventEntry.label || eventEntry.id)}</strong></p>
10631
+ ${renderInspectorSection("event-pose", "identity", "Identity", `${renderTextField("Object", "pose-object-id", pose.objectId)}
10632
+ <div class="wo-editor-inline-actions">
10633
+ <button type="button" data-path-kind="event" data-path-id="${escapeHtml3(eventEntry.id)}">Select event</button>
10634
+ </div>`, true)}
10635
+ ${renderInspectorSection("event-pose", "placement", "Placement", `${renderSelectField("Placement mode", "placement-mode", [
10636
+ ["", "None"],
10637
+ ["orbit", "Orbit"],
10638
+ ["at", "At"],
10639
+ ["surface", "Surface"],
10640
+ ["free", "Free"]
10641
+ ], placementMode)}
10642
+ ${renderTextField("Placement target", "placement-target", placementTarget)}
10643
+ ${renderTextField("Free value", "placement-free", freeValue)}
10644
+ ${renderTextField("Distance", "placement-distance", pose.placement?.mode === "orbit" && pose.placement.distance ? formatUnitValue3(pose.placement.distance) : "")}
10645
+ ${renderTextField("Semi-major", "placement-semiMajor", pose.placement?.mode === "orbit" && pose.placement.semiMajor ? formatUnitValue3(pose.placement.semiMajor) : "")}
10646
+ ${renderTextField("Eccentricity", "placement-eccentricity", pose.placement?.mode === "orbit" && pose.placement.eccentricity !== void 0 ? String(pose.placement.eccentricity) : "")}
10647
+ ${renderTextField("Period", "placement-period", pose.placement?.mode === "orbit" && pose.placement.period ? formatUnitValue3(pose.placement.period) : "")}
10648
+ ${renderTextField("Angle", "placement-angle", pose.placement?.mode === "orbit" && pose.placement.angle ? formatUnitValue3(pose.placement.angle) : "")}
10649
+ ${renderTextField("Inclination", "placement-inclination", pose.placement?.mode === "orbit" && pose.placement.inclination ? formatUnitValue3(pose.placement.inclination) : "")}
10650
+ ${renderTextField("Phase", "placement-phase", pose.placement?.mode === "orbit" && pose.placement.phase ? formatUnitValue3(pose.placement.phase) : "")}
10651
+ ${renderTextField("Inner", "prop-inner", pose.inner ? formatUnitValue3(pose.inner) : "")}
10652
+ ${renderTextField("Outer", "prop-outer", pose.outer ? formatUnitValue3(pose.outer) : "")}`, true)}
10653
+ ${renderInspectorSection("event-pose", "context", "Context", `${renderTextField("Epoch", "pose-epoch", pose.epoch ?? "")}
10654
+ ${renderTextField("Reference plane", "pose-referencePlane", pose.referencePlane ?? "")}
10655
+ <p class="wo-editor-inline-note">Falls back to event, then object, then system context when left empty.</p>`)}
9199
10656
  </form>`;
9200
10657
  }
9201
10658
  function renderAnnotationInspector(formState, id) {
@@ -9300,13 +10757,19 @@ var WorldOrbit = (() => {
9300
10757
  return form.elements.namedItem(name)?.checked ?? false;
9301
10758
  }
9302
10759
  function buildPlacementFromForm(form, current) {
10760
+ return buildPlacementFromValues(form, current.placement, current.id);
10761
+ }
10762
+ function buildPlacementFromPoseForm(form, current) {
10763
+ return buildPlacementFromValues(form, current.placement, current.objectId);
10764
+ }
10765
+ function buildPlacementFromValues(form, currentPlacement, fallbackTarget) {
9303
10766
  const mode = readTextInput(form, "placement-mode");
9304
10767
  const target = readOptionalTextInput(form, "placement-target");
9305
10768
  switch (mode) {
9306
10769
  case "orbit":
9307
10770
  return {
9308
10771
  mode,
9309
- target: target ?? (current.placement?.mode === "orbit" ? current.placement.target : current.id),
10772
+ target: target ?? (currentPlacement?.mode === "orbit" ? currentPlacement.target : fallbackTarget),
9310
10773
  distance: parseOptionalUnit(readOptionalTextInput(form, "placement-distance")),
9311
10774
  semiMajor: parseOptionalUnit(readOptionalTextInput(form, "placement-semiMajor")),
9312
10775
  eccentricity: parseNullableNumber(readOptionalTextInput(form, "placement-eccentricity")) ?? void 0,
@@ -9318,13 +10781,13 @@ var WorldOrbit = (() => {
9318
10781
  case "at":
9319
10782
  return {
9320
10783
  mode,
9321
- target: target ?? current.id,
9322
- reference: parseAtReferenceString(target ?? current.id)
10784
+ target: target ?? fallbackTarget,
10785
+ reference: parseAtReferenceString(target ?? fallbackTarget)
9323
10786
  };
9324
10787
  case "surface":
9325
10788
  return {
9326
10789
  mode,
9327
- target: target ?? current.id
10790
+ target: target ?? fallbackTarget
9328
10791
  };
9329
10792
  case "free": {
9330
10793
  const freeValue = readOptionalTextInput(form, "placement-free");
@@ -9374,6 +10837,15 @@ var WorldOrbit = (() => {
9374
10837
  const parsed = Number(value);
9375
10838
  return Number.isFinite(parsed) ? parsed : null;
9376
10839
  }
10840
+ function buildViewCameraFromForm(form) {
10841
+ const camera = {
10842
+ azimuth: parseNullableNumber(readOptionalTextInput(form, "viewpoint-camera-azimuth")),
10843
+ elevation: parseNullableNumber(readOptionalTextInput(form, "viewpoint-camera-elevation")),
10844
+ roll: parseNullableNumber(readOptionalTextInput(form, "viewpoint-camera-roll")),
10845
+ distance: parseNullableNumber(readOptionalTextInput(form, "viewpoint-camera-distance"))
10846
+ };
10847
+ return camera.azimuth !== null || camera.elevation !== null || camera.roll !== null || camera.distance !== null ? camera : null;
10848
+ }
9377
10849
  function parseObjectTypes(value) {
9378
10850
  const tokens = splitTokens(value);
9379
10851
  return tokens.filter((token) => OBJECT_TYPES.includes(token));
@@ -9455,9 +10927,48 @@ var WorldOrbit = (() => {
9455
10927
  annotation.sourceObjectId = toId;
9456
10928
  }
9457
10929
  }
10930
+ for (const eventEntry of document2.events) {
10931
+ if (eventEntry.targetObjectId === fromId) {
10932
+ eventEntry.targetObjectId = toId;
10933
+ }
10934
+ eventEntry.participantObjectIds = eventEntry.participantObjectIds.map((entry) => entry === fromId ? toId : entry);
10935
+ for (const pose of eventEntry.positions) {
10936
+ if (pose.objectId === fromId) {
10937
+ pose.objectId = toId;
10938
+ }
10939
+ if (pose.placement?.mode === "orbit" && pose.placement.target === fromId) {
10940
+ pose.placement.target = toId;
10941
+ }
10942
+ if (pose.placement?.mode === "surface" && pose.placement.target === fromId) {
10943
+ pose.placement.target = toId;
10944
+ }
10945
+ if (pose.placement?.mode === "at") {
10946
+ const reference = pose.placement.reference;
10947
+ if (reference.kind === "anchor" && reference.objectId === fromId) {
10948
+ reference.objectId = toId;
10949
+ }
10950
+ if (reference.kind === "lagrange") {
10951
+ if (reference.primary === fromId) {
10952
+ reference.primary = toId;
10953
+ }
10954
+ if (reference.secondary === fromId) {
10955
+ reference.secondary = toId;
10956
+ }
10957
+ }
10958
+ pose.placement.target = formatAtReference2(reference);
10959
+ }
10960
+ }
10961
+ eventEntry.positions.sort(compareEventPoses);
10962
+ }
9458
10963
  }
9459
10964
  function removeSelectedNode(document2, selection) {
9460
10965
  const next = removeAtlasDocumentNode(document2, selection);
10966
+ if (selection.kind === "event" && selection.id) {
10967
+ for (const viewpoint of next.system?.viewpoints ?? []) {
10968
+ viewpoint.events = viewpoint.events.filter((eventId) => eventId !== selection.id);
10969
+ }
10970
+ return next;
10971
+ }
9461
10972
  if (selection.kind !== "object" || !selection.id) {
9462
10973
  return next;
9463
10974
  }
@@ -9492,9 +11003,45 @@ var WorldOrbit = (() => {
9492
11003
  annotation.sourceObjectId = null;
9493
11004
  }
9494
11005
  }
11006
+ for (const eventEntry of next.events) {
11007
+ if (eventEntry.targetObjectId === selection.id) {
11008
+ eventEntry.targetObjectId = null;
11009
+ }
11010
+ eventEntry.participantObjectIds = eventEntry.participantObjectIds.filter((entry) => entry !== selection.id);
11011
+ eventEntry.positions = eventEntry.positions.filter((entry) => entry.objectId !== selection.id);
11012
+ for (const pose of eventEntry.positions) {
11013
+ if (pose.placement?.mode === "orbit" && pose.placement.target === selection.id) {
11014
+ pose.placement = null;
11015
+ }
11016
+ if (pose.placement?.mode === "surface" && pose.placement.target === selection.id) {
11017
+ pose.placement = null;
11018
+ }
11019
+ if (pose.placement?.mode === "at") {
11020
+ const reference = pose.placement.reference;
11021
+ const touchesSelection = reference.kind === "anchor" && reference.objectId === selection.id || reference.kind === "lagrange" && (reference.primary === selection.id || reference.secondary === selection.id);
11022
+ if (touchesSelection) {
11023
+ pose.placement = null;
11024
+ }
11025
+ }
11026
+ }
11027
+ }
9495
11028
  return next;
9496
11029
  }
9497
- function updateOrbitPhase(document2, objectId, details, pointer) {
11030
+ function findEditablePlacementOwner(document2, path, objectId) {
11031
+ if (path.kind === "event-pose" && path.id && path.key) {
11032
+ const pose = findEventPose2(document2, path.id, path.key);
11033
+ if (pose?.placement) {
11034
+ return { placement: pose.placement };
11035
+ }
11036
+ return null;
11037
+ }
11038
+ const object = findObject2(document2, objectId);
11039
+ if (object?.placement) {
11040
+ return { placement: object.placement };
11041
+ }
11042
+ return null;
11043
+ }
11044
+ function updateOrbitPhase(document2, path, objectId, details, pointer) {
9498
11045
  const orbit = details.orbit;
9499
11046
  if (!orbit || details.object.placement?.mode !== "orbit") {
9500
11047
  return document2;
@@ -9505,17 +11052,17 @@ var WorldOrbit = (() => {
9505
11052
  const radians = Math.atan2((unrotated.y - orbit.cy) / Math.max(ry, 1), (unrotated.x - orbit.cx) / Math.max(rx, 1));
9506
11053
  const phaseDeg = normalizeDegrees(radians * 180 / Math.PI);
9507
11054
  const next = cloneAtlasDocument(document2);
9508
- const object = next.objects.find((entry) => entry.id === objectId);
9509
- if (!object || object.placement?.mode !== "orbit") {
11055
+ const placementOwner = findEditablePlacementOwner(next, path, objectId);
11056
+ if (!placementOwner || placementOwner.placement.mode !== "orbit") {
9510
11057
  return document2;
9511
11058
  }
9512
- object.placement.phase = {
11059
+ placementOwner.placement.phase = {
9513
11060
  value: roundNumber(phaseDeg, 2),
9514
11061
  unit: "deg"
9515
11062
  };
9516
11063
  return next;
9517
11064
  }
9518
- function updateOrbitRadius(document2, objectId, details, pointer, dragContext) {
11065
+ function updateOrbitRadius(document2, path, objectId, details, pointer, dragContext) {
9519
11066
  const orbit = details.orbit;
9520
11067
  if (!orbit || details.object.placement?.mode !== "orbit" || !dragContext) {
9521
11068
  return document2;
@@ -9525,47 +11072,47 @@ var WorldOrbit = (() => {
9525
11072
  const nextBaseRadius = Math.max(nextDisplayedRadius - dragContext.radiusOffsetPx, dragContext.innerPx);
9526
11073
  const nextMetric = orbitRadiusPxToMetric(nextBaseRadius, dragContext.innerPx, dragContext.stepPx);
9527
11074
  const next = cloneAtlasDocument(document2);
9528
- const object = next.objects.find((entry) => entry.id === objectId);
9529
- if (!object || object.placement?.mode !== "orbit") {
11075
+ const placementOwner = findEditablePlacementOwner(next, path, objectId);
11076
+ if (!placementOwner || placementOwner.placement.mode !== "orbit") {
9530
11077
  return document2;
9531
11078
  }
9532
- const currentValue = object.placement.semiMajor ?? object.placement.distance ?? {
11079
+ const currentValue = placementOwner.placement.semiMajor ?? placementOwner.placement.distance ?? {
9533
11080
  value: 1,
9534
11081
  unit: "au"
9535
11082
  };
9536
11083
  const scaled = distanceMetricToUnitValue(Math.max(nextMetric, 0), dragContext.preferredUnit ?? currentValue.unit);
9537
- if (object.placement.semiMajor) {
9538
- object.placement.semiMajor = scaled;
11084
+ if (placementOwner.placement.semiMajor) {
11085
+ placementOwner.placement.semiMajor = scaled;
9539
11086
  } else {
9540
- object.placement.distance = scaled;
11087
+ placementOwner.placement.distance = scaled;
9541
11088
  }
9542
11089
  return next;
9543
11090
  }
9544
- function updateAtReference(document2, objectId, scene, pointer) {
11091
+ function updateAtReference(document2, path, objectId, scene, pointer) {
9545
11092
  const candidate = findNearestAtCandidate(scene, objectId, pointer);
9546
11093
  if (!candidate) {
9547
11094
  return document2;
9548
11095
  }
9549
11096
  const next = cloneAtlasDocument(document2);
9550
- const object = next.objects.find((entry) => entry.id === objectId);
9551
- if (!object || object.placement?.mode !== "at") {
11097
+ const placementOwner = findEditablePlacementOwner(next, path, objectId);
11098
+ if (!placementOwner || placementOwner.placement.mode !== "at") {
9552
11099
  return document2;
9553
11100
  }
9554
- object.placement.reference = candidate.reference;
9555
- object.placement.target = formatAtReference2(candidate.reference);
11101
+ placementOwner.placement.reference = candidate.reference;
11102
+ placementOwner.placement.target = formatAtReference2(candidate.reference);
9556
11103
  return next;
9557
11104
  }
9558
- function updateSurfaceTarget(document2, objectId, scene, pointer) {
11105
+ function updateSurfaceTarget(document2, path, objectId, scene, pointer) {
9559
11106
  const target = findNearestSceneObject(scene, objectId, pointer, (entry) => SURFACE_TARGET_TYPES3.has(entry.object.type));
9560
11107
  if (!target) {
9561
11108
  return document2;
9562
11109
  }
9563
11110
  const next = cloneAtlasDocument(document2);
9564
- const object = next.objects.find((entry) => entry.id === objectId);
9565
- if (!object || object.placement?.mode !== "surface") {
11111
+ const placementOwner = findEditablePlacementOwner(next, path, objectId);
11112
+ if (!placementOwner || placementOwner.placement.mode !== "surface") {
9566
11113
  return document2;
9567
11114
  }
9568
- object.placement.target = target.objectId;
11115
+ placementOwner.placement.target = target.objectId;
9569
11116
  return next;
9570
11117
  }
9571
11118
  function createOrbitRadiusDragContext(document2, scene, details) {
@@ -9573,7 +11120,7 @@ var WorldOrbit = (() => {
9573
11120
  return null;
9574
11121
  }
9575
11122
  const targetId = details.object.placement.target;
9576
- const siblingCount = document2.objects.filter((entry) => entry.placement?.mode === "orbit" && entry.placement.target === targetId).length;
11123
+ const siblingCount = scene.objects.filter((entry) => entry.object.placement?.mode === "orbit" && entry.object.placement.target === targetId && !entry.hidden).length;
9577
11124
  const spacingFactor = layoutPresetSpacingForScene(scene.layoutPreset);
9578
11125
  const stepPx = (siblingCount > 2 ? 54 : 64) * spacingFactor * scene.scaleModel.orbitDistanceMultiplier;
9579
11126
  const innerPx = details.parent.radius + 56 * spacingFactor * scene.scaleModel.orbitDistanceMultiplier;
@@ -9588,28 +11135,28 @@ var WorldOrbit = (() => {
9588
11135
  preferredUnit: currentValue?.unit ?? null
9589
11136
  };
9590
11137
  }
9591
- function updateFreeDistance(document2, objectId, scene, details, pointer) {
11138
+ function updateFreeDistance(document2, path, objectId, scene, details, pointer) {
9592
11139
  if (details.object.placement?.mode !== "free") {
9593
11140
  return document2;
9594
11141
  }
9595
11142
  const railX = scene.width - scene.padding - 140;
9596
11143
  const offsetPx = Math.max(0, railX - pointer.x);
9597
11144
  const next = cloneAtlasDocument(document2);
9598
- const object = next.objects.find((entry) => entry.id === objectId);
9599
- if (!object || object.placement?.mode !== "free") {
11145
+ const placementOwner = findEditablePlacementOwner(next, path, objectId);
11146
+ if (!placementOwner || placementOwner.placement.mode !== "free") {
9600
11147
  return document2;
9601
11148
  }
9602
- const preferredUnit = normalizeFreeDistanceUnit(object.placement.distance?.unit ?? null);
11149
+ const preferredUnit = normalizeFreeDistanceUnit(placementOwner.placement.distance?.unit ?? null);
9603
11150
  const metric = offsetPx / Math.max(FREE_DISTANCE_PIXEL_FACTOR * scene.scaleModel.freePlacementMultiplier, 1);
9604
11151
  if (metric < 0.01) {
9605
- object.placement.distance = void 0;
9606
- if (!object.placement.descriptor) {
9607
- delete object.placement.descriptor;
11152
+ placementOwner.placement.distance = void 0;
11153
+ if (!placementOwner.placement.descriptor) {
11154
+ delete placementOwner.placement.descriptor;
9608
11155
  }
9609
11156
  return next;
9610
11157
  }
9611
- object.placement.distance = distanceMetricToUnitValue(metric, preferredUnit);
9612
- delete object.placement.descriptor;
11158
+ placementOwner.placement.distance = distanceMetricToUnitValue(metric, preferredUnit);
11159
+ delete placementOwner.placement.descriptor;
9613
11160
  return next;
9614
11161
  }
9615
11162
  function findNearestSceneObject(scene, selectedObjectId, pointer, predicate = () => true) {
@@ -9881,9 +11428,85 @@ var WorldOrbit = (() => {
9881
11428
  return ["viewpoint-zoom"];
9882
11429
  case "rotationDeg":
9883
11430
  return ["viewpoint-rotation"];
11431
+ case "camera":
11432
+ return [
11433
+ "viewpoint-camera-azimuth",
11434
+ "viewpoint-camera-elevation",
11435
+ "viewpoint-camera-roll",
11436
+ "viewpoint-camera-distance"
11437
+ ];
11438
+ case "camera.azimuth":
11439
+ return ["viewpoint-camera-azimuth"];
11440
+ case "camera.elevation":
11441
+ return ["viewpoint-camera-elevation"];
11442
+ case "camera.roll":
11443
+ return ["viewpoint-camera-roll"];
11444
+ case "camera.distance":
11445
+ return ["viewpoint-camera-distance"];
11446
+ case "events":
11447
+ return ["viewpoint-events"];
11448
+ default:
11449
+ return [];
11450
+ }
11451
+ case "event":
11452
+ switch (field) {
11453
+ case "id":
11454
+ return ["event-id"];
11455
+ case "kind":
11456
+ return ["event-kind"];
11457
+ case "label":
11458
+ return ["event-label"];
11459
+ case "summary":
11460
+ return ["event-summary"];
11461
+ case "targetObjectId":
11462
+ case "target":
11463
+ return ["event-target"];
11464
+ case "participantObjectIds":
11465
+ case "participants":
11466
+ return ["event-participants"];
11467
+ case "timing":
11468
+ return ["event-timing"];
11469
+ case "visibility":
11470
+ return ["event-visibility"];
11471
+ case "epoch":
11472
+ return ["event-epoch"];
11473
+ case "referencePlane":
11474
+ return ["event-referencePlane"];
11475
+ case "tags":
11476
+ return ["event-tags"];
11477
+ case "color":
11478
+ return ["event-color"];
11479
+ case "hidden":
11480
+ return ["event-hidden"];
9884
11481
  default:
9885
11482
  return [];
9886
11483
  }
11484
+ case "event-pose":
11485
+ if (field === "objectId") {
11486
+ return ["pose-object-id"];
11487
+ }
11488
+ if (field === "placement") {
11489
+ return ["placement-mode"];
11490
+ }
11491
+ if (field === "reference" || field === "target") {
11492
+ return ["placement-target"];
11493
+ }
11494
+ if (field === "descriptor") {
11495
+ return ["placement-free"];
11496
+ }
11497
+ if (PLACEMENT_DIAGNOSTIC_FIELDS.has(field)) {
11498
+ return [`placement-${field}`];
11499
+ }
11500
+ if (field === "inner" || field === "outer") {
11501
+ return [`prop-${field}`];
11502
+ }
11503
+ if (field === "epoch") {
11504
+ return ["pose-epoch"];
11505
+ }
11506
+ if (field === "referencePlane") {
11507
+ return ["pose-referencePlane"];
11508
+ }
11509
+ return [];
9887
11510
  case "annotation":
9888
11511
  switch (field) {
9889
11512
  case "id":
@@ -9969,6 +11592,10 @@ var WorldOrbit = (() => {
9969
11592
  return `Metadata: ${path.key ?? ""}`;
9970
11593
  case "group":
9971
11594
  return `Group: ${path.id ?? ""}`;
11595
+ case "event":
11596
+ return `Event: ${path.id ?? ""}`;
11597
+ case "event-pose":
11598
+ return `Event Pose: ${path.id ?? ""} / ${path.key ?? ""}`;
9972
11599
  case "object":
9973
11600
  return `Object: ${path.id ?? ""}`;
9974
11601
  case "viewpoint":
@@ -9980,11 +11607,70 @@ var WorldOrbit = (() => {
9980
11607
  }
9981
11608
  }
9982
11609
  function selectionKey(path) {
9983
- return path ? `${path.kind}:${path.id ?? path.key ?? ""}` : null;
11610
+ return path ? `${path.kind}:${path.id ?? ""}:${path.key ?? ""}` : null;
11611
+ }
11612
+ function selectionEventId(path) {
11613
+ if (!path) {
11614
+ return null;
11615
+ }
11616
+ return path.kind === "event" || path.kind === "event-pose" ? path.id ?? null : null;
9984
11617
  }
9985
11618
  function compareObjects2(left, right) {
9986
11619
  return left.id.localeCompare(right.id);
9987
11620
  }
11621
+ function compareEvents(left, right) {
11622
+ return left.id.localeCompare(right.id);
11623
+ }
11624
+ function compareEventPoses(left, right) {
11625
+ return left.objectId.localeCompare(right.objectId);
11626
+ }
11627
+ function findEvent2(document2, eventId) {
11628
+ return document2.events.find((entry) => entry.id === eventId) ?? null;
11629
+ }
11630
+ function findEventPose2(document2, eventId, objectId) {
11631
+ return findEvent2(document2, eventId)?.positions.find((entry) => entry.objectId === objectId) ?? null;
11632
+ }
11633
+ function findObject2(document2, objectId) {
11634
+ return document2.objects.find((entry) => entry.id === objectId) ?? null;
11635
+ }
11636
+ function addEventPose(document2, eventId) {
11637
+ const next = cloneAtlasDocument(document2);
11638
+ const eventEntry = next.events.find((entry) => entry.id === eventId);
11639
+ if (!eventEntry) {
11640
+ return document2;
11641
+ }
11642
+ const baseObject = next.objects.find((object) => !eventEntry.positions.some((pose) => pose.objectId === object.id)) ?? next.objects[0];
11643
+ if (!baseObject) {
11644
+ return document2;
11645
+ }
11646
+ if (eventEntry.targetObjectId !== baseObject.id && !eventEntry.participantObjectIds.includes(baseObject.id)) {
11647
+ eventEntry.participantObjectIds.push(baseObject.id);
11648
+ eventEntry.participantObjectIds.sort((left, right) => left.localeCompare(right));
11649
+ }
11650
+ eventEntry.positions.push(createEventPoseFromObject(baseObject));
11651
+ eventEntry.positions.sort(compareEventPoses);
11652
+ return next;
11653
+ }
11654
+ function createEventPoseFromObject(object) {
11655
+ return {
11656
+ objectId: object.id,
11657
+ placement: object.placement ? structuredClone(object.placement) : null,
11658
+ inner: readUnitValue(object.properties.inner),
11659
+ outer: readUnitValue(object.properties.outer)
11660
+ };
11661
+ }
11662
+ function syncEventViewpointReferences(document2, previousEventId, nextEventId, viewpointIds) {
11663
+ const desired = new Set(viewpointIds);
11664
+ for (const viewpoint of document2.system?.viewpoints ?? []) {
11665
+ const currentIds = new Set(viewpoint.events);
11666
+ currentIds.delete(previousEventId);
11667
+ currentIds.delete(nextEventId);
11668
+ if (desired.has(viewpoint.id)) {
11669
+ currentIds.add(nextEventId);
11670
+ }
11671
+ viewpoint.events = [...currentIds].sort((left, right) => left.localeCompare(right));
11672
+ }
11673
+ }
9988
11674
  function createUniqueId(prefix, existing) {
9989
11675
  const safePrefix = prefix.trim() || "item";
9990
11676
  let counter = 1;
@@ -10013,6 +11699,9 @@ var WorldOrbit = (() => {
10013
11699
  function readUnitProperty(value) {
10014
11700
  return value && typeof value === "object" && "value" in value ? formatUnitValue3(value) : "";
10015
11701
  }
11702
+ function readUnitValue(value) {
11703
+ return value && typeof value === "object" && "value" in value ? value : void 0;
11704
+ }
10016
11705
  function readNumberProperty(value) {
10017
11706
  return typeof value === "number" ? String(value) : "";
10018
11707
  }
@@ -10318,6 +12007,12 @@ var WorldOrbit = (() => {
10318
12007
  .wo-editor-overlay-diagnostic-warning { border: 1px solid rgba(240, 180, 100, 0.24); }
10319
12008
  .wo-editor-outline { display: grid; gap: 14px; }
10320
12009
  .wo-editor-outline-section { display: grid; gap: 8px; }
12010
+ .wo-editor-outline-group { display: grid; gap: 6px; }
12011
+ .wo-editor-outline-children {
12012
+ display: grid;
12013
+ gap: 6px;
12014
+ padding-left: 16px;
12015
+ }
10321
12016
  .wo-editor-outline-section h3 {
10322
12017
  margin: 0;
10323
12018
  color: rgba(237,246,255,0.68);
@@ -10357,6 +12052,27 @@ var WorldOrbit = (() => {
10357
12052
  background: rgba(255, 120, 120, 0.18);
10358
12053
  color: #ffb2b2;
10359
12054
  }
12055
+ .wo-editor-inline-list { display: grid; gap: 8px; }
12056
+ .wo-editor-inline-actions {
12057
+ display: flex;
12058
+ flex-wrap: wrap;
12059
+ gap: 10px;
12060
+ margin-top: 12px;
12061
+ }
12062
+ .wo-editor-inline-actions button {
12063
+ border: 1px solid rgba(255,255,255,0.14);
12064
+ border-radius: 999px;
12065
+ background: rgba(255,255,255,0.06);
12066
+ color: #edf6ff;
12067
+ cursor: pointer;
12068
+ font: 600 12px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
12069
+ padding: 8px 12px;
12070
+ }
12071
+ .wo-editor-inline-note {
12072
+ margin: 0 0 12px;
12073
+ color: rgba(237,246,255,0.72);
12074
+ font: 500 12px/1.5 "Segoe UI Variable", "Segoe UI", sans-serif;
12075
+ }
10360
12076
  .wo-editor-diagnostics { display: grid; gap: 10px; }
10361
12077
  .wo-editor-diagnostic {
10362
12078
  display: grid;