worldorbit 2.5.13 → 2.5.16

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 (41) hide show
  1. package/README.md +37 -11
  2. package/dist/browser/core/dist/index.js +1811 -386
  3. package/dist/browser/editor/dist/index.js +10534 -0
  4. package/dist/browser/markdown/dist/index.js +1477 -221
  5. package/dist/browser/viewer/dist/index.js +1569 -230
  6. package/dist/unpkg/core/dist/index.js +1814 -389
  7. package/dist/unpkg/editor/dist/index.js +10559 -0
  8. package/dist/unpkg/markdown/dist/index.js +1480 -224
  9. package/dist/unpkg/viewer/dist/index.js +1572 -233
  10. package/dist/unpkg/worldorbit-core.min.js +12 -5
  11. package/dist/unpkg/worldorbit-editor.min.js +812 -0
  12. package/dist/unpkg/worldorbit-markdown.min.js +32 -23
  13. package/dist/unpkg/worldorbit-viewer.min.js +55 -41
  14. package/dist/unpkg/worldorbit.js +1713 -231
  15. package/dist/unpkg/worldorbit.min.js +58 -44
  16. package/package.json +3 -2
  17. package/packages/core/README.md +5 -1
  18. package/packages/core/dist/atlas-edit.d.ts +2 -2
  19. package/packages/core/dist/atlas-edit.js +70 -7
  20. package/packages/core/dist/atlas-utils.d.ts +22 -0
  21. package/packages/core/dist/atlas-utils.js +189 -0
  22. package/packages/core/dist/atlas-validate.d.ts +2 -0
  23. package/packages/core/dist/atlas-validate.js +285 -0
  24. package/packages/core/dist/draft-parse.js +786 -153
  25. package/packages/core/dist/draft.d.ts +3 -0
  26. package/packages/core/dist/draft.js +47 -3
  27. package/packages/core/dist/format.js +165 -9
  28. package/packages/core/dist/load.js +58 -13
  29. package/packages/core/dist/normalize.js +7 -0
  30. package/packages/core/dist/scene.js +66 -13
  31. package/packages/core/dist/types.d.ts +97 -3
  32. package/packages/editor/dist/editor.js +44 -0
  33. package/packages/markdown/README.md +1 -1
  34. package/packages/viewer/README.md +2 -1
  35. package/packages/viewer/dist/atlas-state.js +7 -1
  36. package/packages/viewer/dist/atlas-viewer.js +35 -1
  37. package/packages/viewer/dist/render.js +16 -7
  38. package/packages/viewer/dist/theme.js +4 -0
  39. package/packages/viewer/dist/tooltip.js +35 -0
  40. package/packages/viewer/dist/types.d.ts +7 -0
  41. package/packages/viewer/dist/viewer.js +4 -0
@@ -0,0 +1,285 @@
1
+ const SURFACE_TARGET_TYPES = new Set(["star", "planet", "moon", "asteroid", "comet"]);
2
+ const EARTH_MASSES_PER_SOLAR = 332_946.0487;
3
+ const JUPITER_MASSES_PER_SOLAR = 1_047.3486;
4
+ const AU_IN_KM = 149_597_870.7;
5
+ const EARTH_RADIUS_IN_KM = 6_371;
6
+ const SOLAR_RADIUS_IN_KM = 695_700;
7
+ const LY_IN_AU = 63_241.077;
8
+ const PC_IN_AU = 206_264.806;
9
+ const KPC_IN_AU = 206_264_806;
10
+ export function collectAtlasDiagnostics(document, sourceSchemaVersion) {
11
+ const diagnostics = [];
12
+ const objectMap = new Map(document.objects.map((object) => [object.id, object]));
13
+ const groupIds = new Set(document.groups.map((group) => group.id));
14
+ if (!document.system) {
15
+ diagnostics.push(error("validate.system.required", "Atlas documents must declare exactly one system."));
16
+ }
17
+ const knownIds = new Map();
18
+ for (const [kind, ids] of [
19
+ ["group", document.groups.map((group) => group.id)],
20
+ ["viewpoint", document.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []],
21
+ ["annotation", document.system?.annotations.map((annotation) => annotation.id) ?? []],
22
+ ["relation", document.relations.map((relation) => relation.id)],
23
+ ["object", document.objects.map((object) => object.id)],
24
+ ]) {
25
+ for (const id of ids) {
26
+ const previous = knownIds.get(id);
27
+ if (previous) {
28
+ diagnostics.push(error("validate.id.duplicate", `Duplicate ${kind} id "${id}" already used by ${previous}.`));
29
+ }
30
+ else {
31
+ knownIds.set(id, kind);
32
+ }
33
+ }
34
+ }
35
+ for (const relation of document.relations) {
36
+ validateRelation(relation, objectMap, diagnostics);
37
+ }
38
+ for (const viewpoint of document.system?.viewpoints ?? []) {
39
+ validateViewpointFilter(viewpoint.filter, groupIds, sourceSchemaVersion, diagnostics, viewpoint.id);
40
+ }
41
+ for (const object of document.objects) {
42
+ validateObject(object, document.system, objectMap, groupIds, diagnostics);
43
+ }
44
+ return diagnostics;
45
+ }
46
+ function validateRelation(relation, objectMap, diagnostics) {
47
+ if (!relation.from) {
48
+ diagnostics.push(error("validate.relation.from.required", `Relation "${relation.id}" is missing a "from" target.`));
49
+ }
50
+ else if (!objectMap.has(relation.from)) {
51
+ diagnostics.push(error("validate.relation.from.unknown", `Unknown relation source "${relation.from}" on "${relation.id}".`));
52
+ }
53
+ if (!relation.to) {
54
+ diagnostics.push(error("validate.relation.to.required", `Relation "${relation.id}" is missing a "to" target.`));
55
+ }
56
+ else if (!objectMap.has(relation.to)) {
57
+ diagnostics.push(error("validate.relation.to.unknown", `Unknown relation target "${relation.to}" on "${relation.id}".`));
58
+ }
59
+ if (!relation.kind) {
60
+ diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
61
+ }
62
+ }
63
+ function validateViewpointFilter(filter, groupIds, sourceSchemaVersion, diagnostics, viewpointId) {
64
+ if (!filter || sourceSchemaVersion !== "2.1") {
65
+ return;
66
+ }
67
+ for (const groupId of filter.groupIds) {
68
+ if (!groupIds.has(groupId)) {
69
+ diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpointId}".`));
70
+ }
71
+ }
72
+ }
73
+ function validateObject(object, system, objectMap, groupIds, diagnostics) {
74
+ const placement = object.placement;
75
+ const orbitPlacement = placement?.mode === "orbit" ? placement : null;
76
+ const parentObject = placement?.mode === "orbit" ? objectMap.get(placement.target) ?? null : null;
77
+ if (object.groups) {
78
+ for (const groupId of object.groups) {
79
+ if (!groupIds.has(groupId)) {
80
+ diagnostics.push(warn("validate.group.unknown", `Unknown group "${groupId}" on "${object.id}".`, object.id, "groups"));
81
+ }
82
+ }
83
+ }
84
+ if (orbitPlacement) {
85
+ if (!objectMap.has(orbitPlacement.target)) {
86
+ diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
87
+ }
88
+ if (orbitPlacement.distance && orbitPlacement.semiMajor) {
89
+ diagnostics.push(error("validate.orbit.distanceConflict", `Object "${object.id}" cannot declare both "distance" and "semiMajor".`, object.id, "distance"));
90
+ }
91
+ if (orbitPlacement.phase && !object.epoch && !system?.epoch) {
92
+ diagnostics.push(warn("validate.phase.epochMissing", `Object "${object.id}" sets "phase" without an object or system epoch.`, object.id, "phase"));
93
+ }
94
+ if (orbitPlacement.inclination && !object.referencePlane && !system?.referencePlane) {
95
+ diagnostics.push(warn("validate.inclination.referencePlaneMissing", `Object "${object.id}" sets "inclination" without an object or system reference plane.`, object.id, "inclination"));
96
+ }
97
+ if (orbitPlacement.period && !massInSolar(parentObject?.properties.mass)) {
98
+ diagnostics.push(warn("validate.period.massMissing", `Object "${object.id}" sets "period" but its central mass cannot be derived.`, object.id, "period"));
99
+ }
100
+ }
101
+ if (placement?.mode === "surface") {
102
+ const target = objectMap.get(placement.target);
103
+ if (!target) {
104
+ diagnostics.push(error("validate.surface.target.unknown", `Unknown placement target "${placement.target}" on "${object.id}".`, object.id, "surface"));
105
+ }
106
+ else if (!SURFACE_TARGET_TYPES.has(target.type)) {
107
+ diagnostics.push(error("validate.surface.target.invalid", `Surface target "${placement.target}" on "${object.id}" is not surface-capable.`, object.id, "surface"));
108
+ }
109
+ }
110
+ if (placement?.mode === "at") {
111
+ if (object.type !== "structure" && object.type !== "phenomenon") {
112
+ diagnostics.push(error("validate.at.objectType", `Only structures and phenomena may use "at" placement; found "${object.type}" on "${object.id}".`, object.id, "at"));
113
+ }
114
+ if (!validateAtTarget(object, objectMap, diagnostics)) {
115
+ diagnostics.push(error("validate.at.target.unknown", `Unknown at-reference target "${placement.target}" on "${object.id}".`, object.id, "at"));
116
+ }
117
+ }
118
+ if (object.resonance) {
119
+ const target = objectMap.get(object.resonance.targetObjectId);
120
+ if (!target) {
121
+ diagnostics.push(error("validate.resonance.target.unknown", `Unknown resonance target "${object.resonance.targetObjectId}" on "${object.id}".`, object.id, "resonance"));
122
+ }
123
+ else if (object.placement?.mode !== "orbit" ||
124
+ target.placement?.mode !== "orbit" ||
125
+ object.placement.target !== target.placement.target) {
126
+ diagnostics.push(warn("validate.resonance.orbitMismatch", `Resonance target "${object.resonance.targetObjectId}" on "${object.id}" does not share a compatible orbital parent.`, object.id, "resonance"));
127
+ }
128
+ }
129
+ for (const rule of object.deriveRules ?? []) {
130
+ if (rule.field !== "period" || rule.strategy !== "kepler") {
131
+ diagnostics.push(warn("validate.derive.unsupported", `Unsupported derive rule "${rule.field} ${rule.strategy}" on "${object.id}".`, object.id, "derive"));
132
+ continue;
133
+ }
134
+ const derivedPeriodDays = keplerPeriodDays(object, parentObject);
135
+ if (derivedPeriodDays === null) {
136
+ diagnostics.push(warn("validate.derive.inputsMissing", `Object "${object.id}" requests "derive period kepler" but lacks enough input data.`, object.id, "derive"));
137
+ continue;
138
+ }
139
+ if (!orbitPlacement?.period) {
140
+ diagnostics.push(info("validate.derive.period.available", `Object "${object.id}" can derive a Kepler period of ${formatDays(derivedPeriodDays)}.`, object.id, "derive"));
141
+ }
142
+ }
143
+ for (const rule of object.validationRules ?? []) {
144
+ if (rule.rule !== "kepler") {
145
+ diagnostics.push(warn("validate.rule.unsupported", `Unsupported validation rule "${rule.rule}" on "${object.id}".`, object.id, "validate"));
146
+ continue;
147
+ }
148
+ const actualPeriodDays = durationInDays(orbitPlacement?.period);
149
+ const derivedPeriodDays = keplerPeriodDays(object, parentObject);
150
+ if (actualPeriodDays === null || derivedPeriodDays === null) {
151
+ continue;
152
+ }
153
+ const toleranceDays = toleranceForField(object, "period");
154
+ if (Math.abs(actualPeriodDays - derivedPeriodDays) > toleranceDays) {
155
+ diagnostics.push(error("validate.kepler.mismatch", `Object "${object.id}" fails Kepler validation for "period".`, object.id, "validate"));
156
+ }
157
+ }
158
+ }
159
+ function validateAtTarget(object, objectMap, diagnostics) {
160
+ const reference = object.placement?.mode === "at" ? object.placement.reference : null;
161
+ if (!reference) {
162
+ return true;
163
+ }
164
+ if (reference.kind === "named") {
165
+ return objectMap.has(reference.name);
166
+ }
167
+ if (reference.kind === "anchor") {
168
+ if (!objectMap.has(reference.objectId)) {
169
+ diagnostics.push(error("validate.anchor.target.unknown", `Unknown anchor target "${reference.objectId}" on "${object.id}".`, object.id, "at"));
170
+ return false;
171
+ }
172
+ return true;
173
+ }
174
+ if (!objectMap.has(reference.primary)) {
175
+ diagnostics.push(error("validate.lagrange.primary.unknown", `Unknown Lagrange reference "${reference.primary}" on "${object.id}".`, object.id, "at"));
176
+ return false;
177
+ }
178
+ if (reference.secondary && !objectMap.has(reference.secondary)) {
179
+ diagnostics.push(error("validate.lagrange.secondary.unknown", `Unknown Lagrange reference "${reference.secondary}" on "${object.id}".`, object.id, "at"));
180
+ return false;
181
+ }
182
+ return true;
183
+ }
184
+ function keplerPeriodDays(object, parentObject) {
185
+ const placement = object.placement;
186
+ if (!placement || placement.mode !== "orbit") {
187
+ return null;
188
+ }
189
+ const semiMajorAu = distanceInAu(placement.semiMajor ?? placement.distance);
190
+ const centralMassSolar = massInSolar(parentObject?.properties.mass);
191
+ if (semiMajorAu === null || centralMassSolar === null || centralMassSolar <= 0) {
192
+ return null;
193
+ }
194
+ const periodYears = Math.sqrt((semiMajorAu ** 3) / centralMassSolar);
195
+ return periodYears * 365.25;
196
+ }
197
+ function distanceInAu(value) {
198
+ if (!value)
199
+ return null;
200
+ switch (value.unit) {
201
+ case null:
202
+ case "au":
203
+ return value.value;
204
+ case "km":
205
+ return value.value / AU_IN_KM;
206
+ case "m":
207
+ return value.value / (AU_IN_KM * 1000);
208
+ case "ly":
209
+ return value.value * LY_IN_AU;
210
+ case "pc":
211
+ return value.value * PC_IN_AU;
212
+ case "kpc":
213
+ return value.value * KPC_IN_AU;
214
+ case "re":
215
+ return (value.value * EARTH_RADIUS_IN_KM) / AU_IN_KM;
216
+ case "sol":
217
+ return (value.value * SOLAR_RADIUS_IN_KM) / AU_IN_KM;
218
+ default:
219
+ return null;
220
+ }
221
+ }
222
+ function massInSolar(value) {
223
+ if (!value || typeof value !== "object" || !("value" in value)) {
224
+ return null;
225
+ }
226
+ const unitValue = value;
227
+ switch (unitValue.unit) {
228
+ case null:
229
+ case "sol":
230
+ return unitValue.value;
231
+ case "me":
232
+ return unitValue.value / EARTH_MASSES_PER_SOLAR;
233
+ case "mj":
234
+ return unitValue.value / JUPITER_MASSES_PER_SOLAR;
235
+ default:
236
+ return null;
237
+ }
238
+ }
239
+ function durationInDays(value) {
240
+ if (!value)
241
+ return null;
242
+ switch (value.unit) {
243
+ case null:
244
+ case "d":
245
+ return value.value;
246
+ case "s":
247
+ return value.value / 86_400;
248
+ case "min":
249
+ return value.value / 1_440;
250
+ case "h":
251
+ return value.value / 24;
252
+ case "y":
253
+ return value.value * 365.25;
254
+ case "ky":
255
+ return value.value * 365_250;
256
+ case "my":
257
+ return value.value * 365_250_000;
258
+ case "gy":
259
+ return value.value * 365_250_000_000;
260
+ default:
261
+ return null;
262
+ }
263
+ }
264
+ function toleranceForField(object, field) {
265
+ const tolerance = object.tolerances?.find((entry) => entry.field === field)?.value;
266
+ if (typeof tolerance === "number") {
267
+ return tolerance;
268
+ }
269
+ if (tolerance && typeof tolerance === "object" && "value" in tolerance) {
270
+ return durationInDays(tolerance) ?? 0;
271
+ }
272
+ return 0;
273
+ }
274
+ function formatDays(days) {
275
+ return `${Math.round(days * 100) / 100}d`;
276
+ }
277
+ function error(code, message, objectId, field) {
278
+ return { code, severity: "error", source: "validate", message, objectId, field };
279
+ }
280
+ function warn(code, message, objectId, field) {
281
+ return { code, severity: "warning", source: "validate", message, objectId, field };
282
+ }
283
+ function info(code, message, objectId, field) {
284
+ return { code, severity: "info", source: "validate", message, objectId, field };
285
+ }