worldorbit 4.0.0 → 5.0.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 (48) hide show
  1. package/README.md +17 -18
  2. package/dist/browser/core/dist/draft-parse.js +131 -12
  3. package/dist/browser/core/dist/draft.js +4 -4
  4. package/dist/browser/core/dist/format.js +42 -3
  5. package/dist/browser/core/dist/load.js +7 -2
  6. package/dist/browser/core/dist/scene.js +215 -3
  7. package/dist/browser/core/dist/solver.d.ts +8 -1
  8. package/dist/browser/core/dist/solver.js +72 -0
  9. package/dist/browser/core/dist/spatial-scene.js +56 -0
  10. package/dist/browser/core/dist/types.d.ts +74 -2
  11. package/dist/browser/viewer/dist/render.js +71 -15
  12. package/dist/browser/viewer/dist/theme.js +1 -0
  13. package/dist/browser/viewer/dist/types.d.ts +7 -0
  14. package/dist/browser/viewer/dist/viewer.js +27 -0
  15. package/dist/obsidian-plugin/main.js +81 -67
  16. package/dist/unpkg/core/dist/draft-parse.js +131 -12
  17. package/dist/unpkg/core/dist/draft.js +4 -4
  18. package/dist/unpkg/core/dist/format.js +42 -3
  19. package/dist/unpkg/core/dist/load.js +7 -2
  20. package/dist/unpkg/core/dist/scene.js +215 -3
  21. package/dist/unpkg/core/dist/solver.d.ts +8 -1
  22. package/dist/unpkg/core/dist/solver.js +72 -0
  23. package/dist/unpkg/core/dist/spatial-scene.js +56 -0
  24. package/dist/unpkg/core/dist/types.d.ts +74 -2
  25. package/dist/unpkg/viewer/dist/render.js +71 -15
  26. package/dist/unpkg/viewer/dist/theme.js +1 -0
  27. package/dist/unpkg/viewer/dist/types.d.ts +7 -0
  28. package/dist/unpkg/viewer/dist/viewer.js +27 -0
  29. package/dist/unpkg/worldorbit-core.min.js +12 -12
  30. package/dist/unpkg/worldorbit-editor.min.js +336 -322
  31. package/dist/unpkg/worldorbit-markdown.min.js +50 -36
  32. package/dist/unpkg/worldorbit-viewer.min.js +225 -211
  33. package/dist/unpkg/worldorbit.js +473 -19
  34. package/dist/unpkg/worldorbit.min.js +229 -215
  35. package/package.json +1 -1
  36. package/packages/core/dist/draft-parse.js +131 -12
  37. package/packages/core/dist/draft.js +4 -4
  38. package/packages/core/dist/format.js +42 -3
  39. package/packages/core/dist/load.js +7 -2
  40. package/packages/core/dist/scene.js +215 -3
  41. package/packages/core/dist/solver.d.ts +8 -1
  42. package/packages/core/dist/solver.js +72 -0
  43. package/packages/core/dist/spatial-scene.js +56 -0
  44. package/packages/core/dist/types.d.ts +74 -2
  45. package/packages/viewer/dist/render.js +71 -15
  46. package/packages/viewer/dist/theme.js +1 -0
  47. package/packages/viewer/dist/types.d.ts +7 -0
  48. package/packages/viewer/dist/viewer.js +27 -0
package/README.md CHANGED
@@ -29,7 +29,7 @@ WorldOrbit is not intended to be a real-world astronomy simulator or a high-prec
29
29
  ## Quick Example
30
30
 
31
31
  ```worldorbit
32
- schema 3.0
32
+ schema 3.1
33
33
 
34
34
  system Iyath
35
35
  title "Iyath System"
@@ -138,10 +138,10 @@ For direct browser usage, use the browser bundle:
138
138
  import {
139
139
  createInteractiveViewer,
140
140
  loadWorldOrbitSource
141
- } from "https://unpkg.com/worldorbit@4.0.0/dist/unpkg/worldorbit.esm.js";
141
+ } from "https://unpkg.com/worldorbit@5.0.0/dist/unpkg/worldorbit.esm.js";
142
142
 
143
143
  const source = `
144
- schema 3.0
144
+ schema 3.1
145
145
 
146
146
  system Iyath
147
147
  epoch "JY-0001.0"
@@ -196,10 +196,10 @@ The editor is optional. The core format remains text-first.
196
196
 
197
197
  ## Canonical Schema
198
198
 
199
- New atlas authoring should start with:
199
+ New atlas authoring should start with:
200
200
 
201
201
  ```worldorbit
202
- schema 3.0
202
+ schema 3.1
203
203
  ```
204
204
 
205
205
  Example:
@@ -279,20 +279,19 @@ event naar-eclipse
279
279
  phase 90deg
280
280
  ```
281
281
 
282
- ## What's New In Schema 3.0
282
+ ## What's New In Schema 3.1
283
283
 
284
- Schema `3.0` keeps Schema `2.6` fully readable and expands WorldOrbit from orbit-only worldbuilding into declarative mission and trajectory authoring.
284
+ Schema `3.1` keeps Schema `3.0` and Schema `2.6` fully readable and expands WorldOrbit from declarative mission authoring into visible mission-curve rendering.
285
285
 
286
- It adds:
286
+ It adds:
287
287
 
288
- * a canonical `craft` object type for ships, probes, and stations
289
- * a declarative `trajectory` top-level block for reusable mission paths
290
- * trajectory segments for transfers, departures, flybys, captures, escapes, and stationkeeping
291
- * swing-by and gravity-assist metadata such as `assist`, `turnAngle`, `periapsis`, `deltaV`, and `energy`
292
- * explicit links between `craft`, `trajectory`, `event`, and `pose` snapshots for curated mission states
293
- * a solver-friendly data model without forcing the core parser to perform numerical simulation
288
+ * visible `trajectory` rendering in 2D and 3D scenes
289
+ * official `illustrative`, `solver`, and `auto` trajectory render modes
290
+ * trajectory waypoint labels, dates, marker controls, and per-segment render hints
291
+ * official trajectory sampling APIs through `worldorbit/core/solver`
292
+ * continued support for declarative `craft`, `trajectory`, `event`, and `pose` mission authoring
294
293
 
295
- Schema `3.0` still does **not** add a built-in orbital solver, continuous XYZ path authoring, meshes, materials, quaternions, or lighting.
294
+ Schema `3.1` still does **not** add a built-in N-body simulator, NASA-grade ephemerides, continuous freeform XYZ authoring, or hard astrophysics requirements.
296
295
 
297
296
  Schema `2.6` remains the direct compatibility base, and older documents continue to load through the same parser and normalization pipeline.
298
297
 
@@ -419,7 +418,7 @@ const atlasDocument = upgradeDocumentToV2(stable.document, {
419
418
  preset: "atlas-card",
420
419
  });
421
420
 
422
- const atlasSource = formatDocument(atlasDocument, { schema: "3.0" });
421
+ const atlasSource = formatDocument(atlasDocument, { schema: "3.1" });
423
422
  const loaded = loadWorldOrbitSource(atlasSource);
424
423
  const parsedAtlas = parseWorldOrbitAtlas(atlasSource);
425
424
  const scene = renderDocumentToScene(loaded.document, {
@@ -433,7 +432,7 @@ const scene = renderDocumentToScene(loaded.document, {
433
432
 
434
433
  ## Viewer Capabilities
435
434
 
436
- Viewer features in `v4.0.0` include:
435
+ Viewer features in `v5.0.0` include:
437
436
 
438
437
  * scene-based SVG rendering
439
438
  * renderer-neutral spatial scenes through `renderDocumentToSpatialScene(...)`
@@ -447,7 +446,7 @@ Viewer features in `v4.0.0` include:
447
446
  * viewpoints, filters, search, and bookmark capture
448
447
  * deep-linkable atlas state
449
448
  * embeddable viewer custom elements
450
- * semantic group filters, relation and event overlays, active event scenes, Schema 3.0 camera metadata, mission trajectory overlays, and event/object reference-context detail metadata
449
+ * semantic group filters, relation and event overlays, active event scenes, Schema 3.1 camera and trajectory metadata, visible mission curves, waypoint labels, and event/object reference-context detail metadata
451
450
 
452
451
  ## Markdown Integration
453
452
 
@@ -179,7 +179,7 @@ function parseAtlasSource(source, forcedOutputVersion) {
179
179
  handleSectionLine(section, indent, tokens, lineNumber);
180
180
  }
181
181
  if (!sawSchemaHeader) {
182
- throw new WorldOrbitError('Missing required atlas schema header "schema 2.0" or "schema 3.0"');
182
+ throw new WorldOrbitError('Missing required atlas schema header "schema 2.0", "schema 3.0", or "schema 3.1"');
183
183
  }
184
184
  const objects = objectNodes.map((node) => normalizeDraftObject(node, sourceSchemaVersion, diagnostics));
185
185
  const normalizedEvents = events.map((event) => normalizeDraftEvent(event, eventPoseNodes.get(event.id) ?? []));
@@ -225,21 +225,23 @@ function parseAtlasSource(source, forcedOutputVersion) {
225
225
  function assertDraftSchemaHeader(tokens, line) {
226
226
  if (tokens.length !== 2 ||
227
227
  tokens[0].value.toLowerCase() !== "schema" ||
228
- !["2.0-draft", "2.0", "2.1", "2.5", "2.6", "3.0"].includes(tokens[1].value.toLowerCase())) {
229
- throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", "schema 2.5", "schema 2.6", "schema 3.0", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
228
+ !["2.0-draft", "2.0", "2.1", "2.5", "2.6", "3.0", "3.1"].includes(tokens[1].value.toLowerCase())) {
229
+ throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", "schema 2.5", "schema 2.6", "schema 3.0", "schema 3.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
230
230
  }
231
231
  const version = tokens[1].value.toLowerCase();
232
232
  return version === "2.6"
233
233
  ? "2.6"
234
234
  : version === "3.0"
235
235
  ? "3.0"
236
- : version === "2.5"
237
- ? "2.5"
238
- : version === "2.1"
239
- ? "2.1"
240
- : version === "2.0-draft"
241
- ? "2.0-draft"
242
- : "2.0";
236
+ : version === "3.1"
237
+ ? "3.1"
238
+ : version === "2.5"
239
+ ? "2.5"
240
+ : version === "2.1"
241
+ ? "2.1"
242
+ : version === "2.0-draft"
243
+ ? "2.0-draft"
244
+ : "2.0";
243
245
  }
244
246
  function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, trajectories, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, trajectoryIds, flags) {
245
247
  const keyword = tokens[0]?.value.toLowerCase();
@@ -523,6 +525,12 @@ function startTrajectorySection(tokens, line, trajectories, trajectoryIds, sourc
523
525
  craftObjectId: null,
524
526
  tags: [],
525
527
  color: null,
528
+ renderMode: null,
529
+ stroke: null,
530
+ strokeWidth: null,
531
+ marker: null,
532
+ labelMode: null,
533
+ showWaypoints: null,
526
534
  hidden: false,
527
535
  segments: [],
528
536
  };
@@ -1090,6 +1098,10 @@ function applyTrajectoryField(section, indent, tokens, line) {
1090
1098
  aroundObjectId: null,
1091
1099
  assist: null,
1092
1100
  epoch: null,
1101
+ waypointLabel: null,
1102
+ waypointDate: null,
1103
+ renderHidden: null,
1104
+ sampleDensity: null,
1093
1105
  notes: [],
1094
1106
  maneuvers: [],
1095
1107
  };
@@ -1124,6 +1136,54 @@ function applyTrajectoryField(section, indent, tokens, line) {
1124
1136
  case "color":
1125
1137
  section.trajectory.color = value;
1126
1138
  return;
1139
+ case "rendermode":
1140
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "trajectory.renderMode", {
1141
+ line,
1142
+ column: tokens[0].column,
1143
+ });
1144
+ if (value !== "illustrative" && value !== "solver" && value !== "auto") {
1145
+ throw new WorldOrbitError(`Unknown trajectory render mode "${value}"`, line, tokens[0].column);
1146
+ }
1147
+ section.trajectory.renderMode = value;
1148
+ return;
1149
+ case "stroke":
1150
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "trajectory.stroke", {
1151
+ line,
1152
+ column: tokens[0].column,
1153
+ });
1154
+ section.trajectory.stroke = value;
1155
+ return;
1156
+ case "strokewidth":
1157
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "trajectory.strokeWidth", {
1158
+ line,
1159
+ column: tokens[0].column,
1160
+ });
1161
+ section.trajectory.strokeWidth = parsePositiveNumber(value, line, tokens[0].column, "strokeWidth");
1162
+ return;
1163
+ case "marker":
1164
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "trajectory.marker", {
1165
+ line,
1166
+ column: tokens[0].column,
1167
+ });
1168
+ section.trajectory.marker = value;
1169
+ return;
1170
+ case "labelmode":
1171
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "trajectory.labelMode", {
1172
+ line,
1173
+ column: tokens[0].column,
1174
+ });
1175
+ section.trajectory.labelMode = value;
1176
+ return;
1177
+ case "showwaypoints":
1178
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "trajectory.showWaypoints", {
1179
+ line,
1180
+ column: tokens[0].column,
1181
+ });
1182
+ section.trajectory.showWaypoints = parseAtlasBoolean(value, "showWaypoints", {
1183
+ line,
1184
+ column: tokens[0].column,
1185
+ });
1186
+ return;
1127
1187
  case "hidden":
1128
1188
  section.trajectory.hidden = parseAtlasBoolean(value, "hidden", {
1129
1189
  line,
@@ -1199,6 +1259,37 @@ function applyTrajectorySegmentField(section, tokens, line) {
1199
1259
  case "energy":
1200
1260
  target.energy = parseAtlasUnitValue(value, { line, column: tokens[0].column });
1201
1261
  return;
1262
+ case "waypointlabel":
1263
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "segment.waypointLabel", {
1264
+ line,
1265
+ column: tokens[0].column,
1266
+ });
1267
+ target.waypointLabel = value;
1268
+ return;
1269
+ case "waypointdate":
1270
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "segment.waypointDate", {
1271
+ line,
1272
+ column: tokens[0].column,
1273
+ });
1274
+ target.waypointDate = value;
1275
+ return;
1276
+ case "renderhidden":
1277
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "segment.renderHidden", {
1278
+ line,
1279
+ column: tokens[0].column,
1280
+ });
1281
+ target.renderHidden = parseAtlasBoolean(value, "renderHidden", {
1282
+ line,
1283
+ column: tokens[0].column,
1284
+ });
1285
+ return;
1286
+ case "sampledensity":
1287
+ warnIfSchema31Feature(section.sourceSchemaVersion, section.diagnostics, "segment.sampleDensity", {
1288
+ line,
1289
+ column: tokens[0].column,
1290
+ });
1291
+ target.sampleDensity = parsePositiveNumber(value, line, tokens[0].column, "sampleDensity");
1292
+ return;
1202
1293
  case "notes":
1203
1294
  target.notes = parseTokenList(tokens.slice(1), line, "notes");
1204
1295
  return;
@@ -1345,7 +1436,8 @@ function parseLayerTokens(tokens, line, sourceSchemaVersion, diagnostics) {
1345
1436
  raw === "events" ||
1346
1437
  raw === "objects" ||
1347
1438
  raw === "labels" ||
1348
- raw === "metadata") {
1439
+ raw === "metadata" ||
1440
+ raw === "trajectories") {
1349
1441
  if (raw === "events" && sourceSchemaVersion && diagnostics) {
1350
1442
  warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "layers.events", {
1351
1443
  line,
@@ -1476,6 +1568,12 @@ function parseInlineObjectFields(tokens, line, objectType, sourceSchemaVersion,
1476
1568
  column: keyToken.column,
1477
1569
  });
1478
1570
  }
1571
+ else if (spec.version === "3.1") {
1572
+ warnIfSchema31Feature(sourceSchemaVersion, diagnostics, keyToken.value, {
1573
+ line,
1574
+ column: keyToken.column,
1575
+ });
1576
+ }
1479
1577
  index++;
1480
1578
  const valueTokens = [];
1481
1579
  if (spec.inlineMode === "single") {
@@ -1534,6 +1632,12 @@ function parseObjectField(tokens, line, objectType, sourceSchemaVersion, diagnos
1534
1632
  column: tokens[0].column,
1535
1633
  });
1536
1634
  }
1635
+ else if (spec.version === "3.1") {
1636
+ warnIfSchema31Feature(sourceSchemaVersion, diagnostics, tokens[0].value, {
1637
+ line,
1638
+ column: tokens[0].column,
1639
+ });
1640
+ }
1537
1641
  const field = {
1538
1642
  type: "field",
1539
1643
  key: tokens[0].value,
@@ -1876,6 +1980,19 @@ function warnIfSchema30Feature(sourceSchemaVersion, diagnostics, featureName, lo
1876
1980
  column: location.column,
1877
1981
  });
1878
1982
  }
1983
+ function warnIfSchema31Feature(sourceSchemaVersion, diagnostics, featureName, location) {
1984
+ if (!isSchemaOlderThan(sourceSchemaVersion, "3.1")) {
1985
+ return;
1986
+ }
1987
+ diagnostics.push({
1988
+ code: "parse.schema31.featureCompatibility",
1989
+ severity: "warning",
1990
+ source: "parse",
1991
+ message: `Feature "${featureName}" requires schema 3.1; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
1992
+ line: location.line,
1993
+ column: location.column,
1994
+ });
1995
+ }
1879
1996
  function isSchemaOlderThan(sourceSchemaVersion, requiredVersion) {
1880
1997
  return schemaVersionRank(sourceSchemaVersion) < schemaVersionRank(requiredVersion);
1881
1998
  }
@@ -1893,8 +2010,10 @@ function schemaVersionRank(version) {
1893
2010
  return 4;
1894
2011
  case "3.0":
1895
2012
  return 5;
1896
- default:
2013
+ case "3.1":
1897
2014
  return 6;
2015
+ default:
2016
+ return 7;
1898
2017
  }
1899
2018
  }
1900
2019
  function preprocessAtlasSource(source) {
@@ -18,15 +18,15 @@ export function upgradeDocumentToV2(document, options = {}) {
18
18
  }
19
19
  return {
20
20
  format: "worldorbit",
21
- version: "3.0",
22
- schemaVersion: "3.0",
21
+ version: "3.1",
22
+ schemaVersion: "3.1",
23
23
  sourceVersion: document.version,
24
24
  theme: document.theme ?? null,
25
25
  system,
26
26
  groups: structuredClone(document.groups ?? []),
27
27
  relations: structuredClone(document.relations ?? []),
28
28
  events: structuredClone(document.events ?? []),
29
- trajectories: [],
29
+ trajectories: structuredClone(document.trajectories ?? []),
30
30
  objects: document.objects.map(cloneWorldOrbitObject).map(normalizeLegacyCraftObject),
31
31
  diagnostics,
32
32
  };
@@ -490,7 +490,7 @@ function serializeViewpointLayers(layers) {
490
490
  if (orbitFront !== undefined || orbitBack !== undefined) {
491
491
  tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
492
492
  }
493
- for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
493
+ for (const key of ["background", "guides", "relations", "events", "objects", "trajectories", "labels", "metadata"]) {
494
494
  if (layers[key] !== undefined) {
495
495
  tokens.push(layers[key] ? key : `-${key}`);
496
496
  }
@@ -41,11 +41,13 @@ export function formatDocument(document, options = {}) {
41
41
  schema === "2.5" ||
42
42
  schema === "2.6" ||
43
43
  schema === "3.0" ||
44
+ schema === "3.1" ||
44
45
  schema === "2.0-draft" ||
45
46
  document.version === "2.0" ||
46
47
  document.version === "2.1" ||
47
48
  document.version === "2.5" ||
48
49
  document.version === "3.0" ||
50
+ document.version === "3.1" ||
49
51
  document.version === "2.6" ||
50
52
  document.version === "2.0-draft";
51
53
  if (useDraft) {
@@ -61,7 +63,12 @@ export function formatDocument(document, options = {}) {
61
63
  : upgradeDocumentToDraftV2(document);
62
64
  return formatDraftDocument(legacyDraftDocument);
63
65
  }
64
- const atlasDocument = document.version === "2.0" || document.version === "2.1" || document.version === "2.5" || document.version === "2.6" || document.version === "3.0"
66
+ const atlasDocument = document.version === "2.0" ||
67
+ document.version === "2.1" ||
68
+ document.version === "2.5" ||
69
+ document.version === "2.6" ||
70
+ document.version === "3.0" ||
71
+ document.version === "3.1"
65
72
  ? document
66
73
  : document.version === "2.0-draft"
67
74
  ? {
@@ -70,7 +77,13 @@ export function formatDocument(document, options = {}) {
70
77
  schemaVersion: "2.0",
71
78
  }
72
79
  : upgradeDocumentToV2(document);
73
- if ((schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.6" || schema === "3.0") && atlasDocument.version !== schema) {
80
+ if ((schema === "2.0" ||
81
+ schema === "2.1" ||
82
+ schema === "2.5" ||
83
+ schema === "2.6" ||
84
+ schema === "3.0" ||
85
+ schema === "3.1") &&
86
+ atlasDocument.version !== schema) {
74
87
  return formatAtlasDocument({
75
88
  ...atlasDocument,
76
89
  version: schema,
@@ -534,6 +547,24 @@ function formatAtlasTrajectory(trajectory) {
534
547
  if (trajectory.color) {
535
548
  lines.push(` color ${quoteIfNeeded(trajectory.color)}`);
536
549
  }
550
+ if (trajectory.renderMode) {
551
+ lines.push(` renderMode ${trajectory.renderMode}`);
552
+ }
553
+ if (trajectory.stroke) {
554
+ lines.push(` stroke ${quoteIfNeeded(trajectory.stroke)}`);
555
+ }
556
+ if (trajectory.strokeWidth !== null && trajectory.strokeWidth !== undefined) {
557
+ lines.push(` strokeWidth ${trajectory.strokeWidth}`);
558
+ }
559
+ if (trajectory.marker) {
560
+ lines.push(` marker ${quoteIfNeeded(trajectory.marker)}`);
561
+ }
562
+ if (trajectory.labelMode) {
563
+ lines.push(` labelMode ${quoteIfNeeded(trajectory.labelMode)}`);
564
+ }
565
+ if (trajectory.showWaypoints !== null && trajectory.showWaypoints !== undefined) {
566
+ lines.push(` showWaypoints ${trajectory.showWaypoints ? "true" : "false"}`);
567
+ }
537
568
  if (trajectory.hidden) {
538
569
  lines.push(" hidden true");
539
570
  }
@@ -570,6 +601,14 @@ function formatTrajectorySegmentFields(segment) {
570
601
  ...formatOptionalUnit("phaseAngle", segment.phaseAngle),
571
602
  ...formatOptionalUnit("turnAngle", segment.turnAngle),
572
603
  ...formatOptionalUnit("energy", segment.energy),
604
+ ...(segment.waypointLabel ? [`waypointLabel ${quoteIfNeeded(segment.waypointLabel)}`] : []),
605
+ ...(segment.waypointDate ? [`waypointDate ${quoteIfNeeded(segment.waypointDate)}`] : []),
606
+ ...(segment.renderHidden !== null && segment.renderHidden !== undefined
607
+ ? [`renderHidden ${segment.renderHidden ? "true" : "false"}`]
608
+ : []),
609
+ ...(segment.sampleDensity !== null && segment.sampleDensity !== undefined
610
+ ? [`sampleDensity ${segment.sampleDensity}`]
611
+ : []),
573
612
  ...(segment.notes.length > 0 ? [`notes ${segment.notes.map(quoteIfNeeded).join(" ")}`] : []),
574
613
  ];
575
614
  }
@@ -634,7 +673,7 @@ function formatDraftLayers(layers) {
634
673
  ? "orbits"
635
674
  : "-orbits");
636
675
  }
637
- for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
676
+ for (const key of ["background", "guides", "relations", "events", "objects", "trajectories", "labels", "metadata"]) {
638
677
  if (layers[key] !== undefined) {
639
678
  tokens.push(layers[key] ? key : `-${key}`);
640
679
  }
@@ -5,11 +5,12 @@ import { WorldOrbitError } from "./errors.js";
5
5
  import { normalizeDocument } from "./normalize.js";
6
6
  import { parseWorldOrbit } from "./parse.js";
7
7
  import { validateDocument } from "./validate.js";
8
- const ATLAS_SCHEMA_PATTERN = /^schema\s+(?:2(?:\.0|\.1|\.5|\.6)?|3(?:\.0)?)$/i;
8
+ const ATLAS_SCHEMA_PATTERN = /^schema\s+(?:2(?:\.0|\.1|\.5|\.6)?|3(?:\.0|\.1)?)$/i;
9
9
  const ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
10
10
  const ATLAS_SCHEMA_25_PATTERN = /^schema\s+2\.5$/i;
11
11
  const ATLAS_SCHEMA_26_PATTERN = /^schema\s+2\.6$/i;
12
12
  const ATLAS_SCHEMA_30_PATTERN = /^schema\s+3(?:\.0)?$/i;
13
+ const ATLAS_SCHEMA_31_PATTERN = /^schema\s+3\.1$/i;
13
14
  const LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
14
15
  export function detectWorldOrbitSchemaVersion(source) {
15
16
  for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
@@ -32,6 +33,9 @@ export function detectWorldOrbitSchemaVersion(source) {
32
33
  if (ATLAS_SCHEMA_30_PATTERN.test(trimmed)) {
33
34
  return "3.0";
34
35
  }
36
+ if (ATLAS_SCHEMA_31_PATTERN.test(trimmed)) {
37
+ return "3.1";
38
+ }
35
39
  if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
36
40
  return "2.0";
37
41
  }
@@ -97,7 +101,8 @@ export function loadWorldOrbitSourceWithDiagnostics(source) {
97
101
  schemaVersion === "2.1" ||
98
102
  schemaVersion === "2.5" ||
99
103
  schemaVersion === "2.6" ||
100
- schemaVersion === "3.0") {
104
+ schemaVersion === "3.0" ||
105
+ schemaVersion === "3.1") {
101
106
  return loadAtlasSourceWithDiagnostics(source, schemaVersion);
102
107
  }
103
108
  let ast;