worldorbit 2.6.0 → 3.0.1
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.
- package/README.md +20 -9
- package/dist/browser/core/dist/atlas-edit.d.ts +11 -0
- package/dist/browser/core/dist/atlas-edit.js +347 -0
- package/dist/browser/core/dist/atlas-utils.d.ts +22 -0
- package/dist/browser/core/dist/atlas-utils.js +189 -0
- package/dist/browser/core/dist/atlas-validate.d.ts +2 -0
- package/dist/browser/core/dist/atlas-validate.js +488 -0
- package/dist/browser/core/dist/diagnostics.d.ts +10 -0
- package/dist/browser/core/dist/diagnostics.js +109 -0
- package/dist/browser/core/dist/draft-parse.d.ts +3 -0
- package/dist/browser/core/dist/draft-parse.js +1654 -0
- package/dist/browser/core/dist/draft.d.ts +21 -0
- package/dist/browser/core/dist/draft.js +482 -0
- package/dist/browser/core/dist/errors.d.ts +7 -0
- package/dist/browser/core/dist/errors.js +16 -0
- package/dist/browser/core/dist/format.d.ts +4 -0
- package/dist/browser/core/dist/format.js +613 -0
- package/dist/browser/core/dist/index.d.ts +29 -0
- package/dist/browser/core/dist/index.js +35 -6542
- package/dist/browser/core/dist/load.d.ts +4 -0
- package/dist/browser/core/dist/load.js +182 -0
- package/dist/browser/core/dist/markdown.d.ts +2 -0
- package/dist/browser/core/dist/markdown.js +37 -0
- package/dist/browser/core/dist/normalize.d.ts +2 -0
- package/dist/browser/core/dist/normalize.js +312 -0
- package/dist/browser/core/dist/parse.d.ts +2 -0
- package/dist/browser/core/dist/parse.js +133 -0
- package/dist/browser/core/dist/scene.d.ts +3 -0
- package/dist/browser/core/dist/scene.js +1901 -0
- package/dist/browser/core/dist/schema.d.ts +8 -0
- package/dist/browser/core/dist/schema.js +298 -0
- package/dist/browser/core/dist/spatial-scene.d.ts +3 -0
- package/dist/browser/core/dist/spatial-scene.js +420 -0
- package/dist/browser/core/dist/tokenize.d.ts +4 -0
- package/dist/browser/core/dist/tokenize.js +68 -0
- package/dist/browser/core/dist/types.d.ts +637 -0
- package/dist/browser/core/dist/types.js +1 -0
- package/dist/browser/core/dist/validate.d.ts +2 -0
- package/dist/browser/core/dist/validate.js +56 -0
- package/dist/browser/editor/dist/editor.d.ts +2 -0
- package/dist/browser/editor/dist/editor.js +3700 -0
- package/dist/browser/editor/dist/index.d.ts +2 -0
- package/dist/browser/editor/dist/index.js +1 -12250
- package/dist/browser/editor/dist/types.d.ts +59 -0
- package/dist/browser/editor/dist/types.js +1 -0
- package/dist/browser/markdown/dist/html.d.ts +3 -0
- package/dist/browser/markdown/dist/html.js +64 -0
- package/dist/browser/markdown/dist/index.d.ts +4 -0
- package/dist/browser/markdown/dist/index.js +3 -6179
- package/dist/browser/markdown/dist/rehype.d.ts +10 -0
- package/dist/browser/markdown/dist/rehype.js +49 -0
- package/dist/browser/markdown/dist/remark.d.ts +9 -0
- package/dist/browser/markdown/dist/remark.js +28 -0
- package/dist/browser/markdown/dist/types.d.ts +11 -0
- package/dist/browser/markdown/dist/types.js +1 -0
- package/dist/browser/viewer/dist/atlas-state.d.ts +12 -0
- package/dist/browser/viewer/dist/atlas-state.js +269 -0
- package/dist/browser/viewer/dist/atlas-viewer.d.ts +2 -0
- package/dist/browser/viewer/dist/atlas-viewer.js +495 -0
- package/dist/browser/viewer/dist/custom-element.d.ts +1 -0
- package/dist/browser/viewer/dist/custom-element.js +78 -0
- package/dist/browser/viewer/dist/embed.d.ts +24 -0
- package/dist/browser/viewer/dist/embed.js +172 -0
- package/dist/browser/viewer/dist/errors.d.ts +6 -0
- package/dist/browser/viewer/dist/errors.js +12 -0
- package/dist/browser/viewer/dist/index.d.ts +10 -0
- package/dist/browser/viewer/dist/index.js +9 -8334
- package/dist/browser/viewer/dist/minimap.d.ts +3 -0
- package/dist/browser/viewer/dist/minimap.js +63 -0
- package/dist/browser/viewer/dist/render.d.ts +6 -0
- package/dist/browser/viewer/dist/render.js +670 -0
- package/dist/browser/viewer/dist/runtime-3d.d.ts +19 -0
- package/dist/browser/viewer/dist/runtime-3d.js +494 -0
- package/dist/browser/viewer/dist/theme.d.ts +4 -0
- package/dist/browser/viewer/dist/theme.js +103 -0
- package/dist/browser/viewer/dist/tooltip.d.ts +3 -0
- package/dist/browser/viewer/dist/tooltip.js +198 -0
- package/dist/browser/viewer/dist/types.d.ts +292 -0
- package/dist/browser/viewer/dist/types.js +1 -0
- package/dist/browser/viewer/dist/vendor/three.module.js +53032 -0
- package/dist/browser/viewer/dist/viewer-state.d.ts +19 -0
- package/dist/browser/viewer/dist/viewer-state.js +162 -0
- package/dist/browser/viewer/dist/viewer.d.ts +2 -0
- package/dist/browser/viewer/dist/viewer.js +1662 -0
- package/dist/unpkg/core/dist/atlas-edit.d.ts +11 -0
- package/dist/unpkg/core/dist/atlas-edit.js +347 -0
- package/dist/unpkg/core/dist/atlas-utils.d.ts +22 -0
- package/dist/unpkg/core/dist/atlas-utils.js +189 -0
- package/dist/unpkg/core/dist/atlas-validate.d.ts +2 -0
- package/dist/unpkg/core/dist/atlas-validate.js +488 -0
- package/dist/unpkg/core/dist/diagnostics.d.ts +10 -0
- package/dist/unpkg/core/dist/diagnostics.js +109 -0
- package/dist/unpkg/core/dist/draft-parse.d.ts +3 -0
- package/dist/unpkg/core/dist/draft-parse.js +1654 -0
- package/dist/unpkg/core/dist/draft.d.ts +21 -0
- package/dist/unpkg/core/dist/draft.js +482 -0
- package/dist/unpkg/core/dist/errors.d.ts +7 -0
- package/dist/unpkg/core/dist/errors.js +16 -0
- package/dist/unpkg/core/dist/format.d.ts +4 -0
- package/dist/unpkg/core/dist/format.js +613 -0
- package/dist/unpkg/core/dist/index.d.ts +29 -0
- package/dist/unpkg/core/dist/index.js +35 -6614
- package/dist/unpkg/core/dist/load.d.ts +4 -0
- package/dist/unpkg/core/dist/load.js +182 -0
- package/dist/unpkg/core/dist/markdown.d.ts +2 -0
- package/dist/unpkg/core/dist/markdown.js +37 -0
- package/dist/unpkg/core/dist/normalize.d.ts +2 -0
- package/dist/unpkg/core/dist/normalize.js +312 -0
- package/dist/unpkg/core/dist/parse.d.ts +2 -0
- package/dist/unpkg/core/dist/parse.js +133 -0
- package/dist/unpkg/core/dist/scene.d.ts +3 -0
- package/dist/unpkg/core/dist/scene.js +1901 -0
- package/dist/unpkg/core/dist/schema.d.ts +8 -0
- package/dist/unpkg/core/dist/schema.js +298 -0
- package/dist/unpkg/core/dist/spatial-scene.d.ts +3 -0
- package/dist/unpkg/core/dist/spatial-scene.js +420 -0
- package/dist/unpkg/core/dist/tokenize.d.ts +4 -0
- package/dist/unpkg/core/dist/tokenize.js +68 -0
- package/dist/unpkg/core/dist/types.d.ts +637 -0
- package/dist/unpkg/core/dist/types.js +1 -0
- package/dist/unpkg/core/dist/validate.d.ts +2 -0
- package/dist/unpkg/core/dist/validate.js +56 -0
- package/dist/unpkg/editor/dist/editor.d.ts +2 -0
- package/dist/unpkg/editor/dist/editor.js +3700 -0
- package/dist/unpkg/editor/dist/index.d.ts +2 -0
- package/dist/unpkg/editor/dist/index.js +1 -12275
- package/dist/unpkg/editor/dist/types.d.ts +59 -0
- package/dist/unpkg/editor/dist/types.js +1 -0
- package/dist/unpkg/markdown/dist/html.d.ts +3 -0
- package/dist/unpkg/markdown/dist/html.js +64 -0
- package/dist/unpkg/markdown/dist/index.d.ts +4 -0
- package/dist/unpkg/markdown/dist/index.js +3 -6207
- package/dist/unpkg/markdown/dist/rehype.d.ts +10 -0
- package/dist/unpkg/markdown/dist/rehype.js +49 -0
- package/dist/unpkg/markdown/dist/remark.d.ts +9 -0
- package/dist/unpkg/markdown/dist/remark.js +28 -0
- package/dist/unpkg/markdown/dist/types.d.ts +11 -0
- package/dist/unpkg/markdown/dist/types.js +1 -0
- package/dist/unpkg/viewer/dist/atlas-state.d.ts +12 -0
- package/dist/unpkg/viewer/dist/atlas-state.js +269 -0
- package/dist/unpkg/viewer/dist/atlas-viewer.d.ts +2 -0
- package/dist/unpkg/viewer/dist/atlas-viewer.js +495 -0
- package/dist/unpkg/viewer/dist/custom-element.d.ts +1 -0
- package/dist/unpkg/viewer/dist/custom-element.js +78 -0
- package/dist/unpkg/viewer/dist/embed.d.ts +24 -0
- package/dist/unpkg/viewer/dist/embed.js +172 -0
- package/dist/unpkg/viewer/dist/errors.d.ts +6 -0
- package/dist/unpkg/viewer/dist/errors.js +12 -0
- package/dist/unpkg/viewer/dist/index.d.ts +10 -0
- package/dist/unpkg/viewer/dist/index.js +9 -8391
- package/dist/unpkg/viewer/dist/minimap.d.ts +3 -0
- package/dist/unpkg/viewer/dist/minimap.js +63 -0
- package/dist/unpkg/viewer/dist/render.d.ts +6 -0
- package/dist/unpkg/viewer/dist/render.js +670 -0
- package/dist/unpkg/viewer/dist/runtime-3d.d.ts +19 -0
- package/dist/unpkg/viewer/dist/runtime-3d.js +494 -0
- package/dist/unpkg/viewer/dist/theme.d.ts +4 -0
- package/dist/unpkg/viewer/dist/theme.js +103 -0
- package/dist/unpkg/viewer/dist/tooltip.d.ts +3 -0
- package/dist/unpkg/viewer/dist/tooltip.js +198 -0
- package/dist/unpkg/viewer/dist/types.d.ts +292 -0
- package/dist/unpkg/viewer/dist/types.js +1 -0
- package/dist/unpkg/viewer/dist/vendor/three.module.js +53032 -0
- package/dist/unpkg/viewer/dist/viewer-state.d.ts +19 -0
- package/dist/unpkg/viewer/dist/viewer-state.js +162 -0
- package/dist/unpkg/viewer/dist/viewer.d.ts +2 -0
- package/dist/unpkg/viewer/dist/viewer.js +1662 -0
- package/dist/unpkg/worldorbit-core.min.js +10 -10
- package/dist/unpkg/worldorbit-editor.min.js +4109 -256
- package/dist/unpkg/worldorbit-markdown.min.js +26 -26
- package/dist/unpkg/worldorbit-viewer.min.js +3945 -92
- package/dist/unpkg/worldorbit.js +32219 -199
- package/dist/unpkg/worldorbit.min.js +3949 -96
- package/package.json +1 -1
- package/packages/core/dist/index.d.ts +1 -0
- package/packages/core/dist/index.js +1 -0
- package/packages/core/dist/spatial-scene.d.ts +3 -0
- package/packages/core/dist/spatial-scene.js +420 -0
- package/packages/core/dist/types.d.ts +105 -0
- package/packages/editor/dist/editor.js +25 -4
- package/packages/editor/dist/types.d.ts +4 -0
- package/packages/markdown/dist/html.js +10 -3
- package/packages/viewer/dist/atlas-state.js +3 -0
- package/packages/viewer/dist/atlas-viewer.js +1 -0
- package/packages/viewer/dist/custom-element.js +18 -4
- package/packages/viewer/dist/embed.d.ts +5 -1
- package/packages/viewer/dist/embed.js +58 -24
- package/packages/viewer/dist/errors.d.ts +6 -0
- package/packages/viewer/dist/errors.js +12 -0
- package/packages/viewer/dist/index.d.ts +1 -0
- package/packages/viewer/dist/index.js +1 -0
- package/packages/viewer/dist/runtime-3d.d.ts +19 -0
- package/packages/viewer/dist/runtime-3d.js +494 -0
- package/packages/viewer/dist/types.d.ts +21 -2
- package/packages/viewer/dist/vendor/three.module.js +53032 -0
- package/packages/viewer/dist/viewer.js +501 -41
|
@@ -0,0 +1,488 @@
|
|
|
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
|
+
const eventIds = new Set(document.events.map((event) => event.id));
|
|
15
|
+
if (!document.system) {
|
|
16
|
+
diagnostics.push(error("validate.system.required", "Atlas documents must declare exactly one system."));
|
|
17
|
+
}
|
|
18
|
+
const knownIds = new Map();
|
|
19
|
+
for (const [kind, ids] of [
|
|
20
|
+
["group", document.groups.map((group) => group.id)],
|
|
21
|
+
["viewpoint", document.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []],
|
|
22
|
+
["annotation", document.system?.annotations.map((annotation) => annotation.id) ?? []],
|
|
23
|
+
["relation", document.relations.map((relation) => relation.id)],
|
|
24
|
+
["event", document.events.map((event) => event.id)],
|
|
25
|
+
["object", document.objects.map((object) => object.id)],
|
|
26
|
+
]) {
|
|
27
|
+
for (const id of ids) {
|
|
28
|
+
const previous = knownIds.get(id);
|
|
29
|
+
if (previous) {
|
|
30
|
+
diagnostics.push(error("validate.id.duplicate", `Duplicate ${kind} id "${id}" already used by ${previous}.`));
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
knownIds.set(id, kind);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (const relation of document.relations) {
|
|
38
|
+
validateRelation(relation, objectMap, diagnostics);
|
|
39
|
+
}
|
|
40
|
+
for (const viewpoint of document.system?.viewpoints ?? []) {
|
|
41
|
+
validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap);
|
|
42
|
+
}
|
|
43
|
+
for (const object of document.objects) {
|
|
44
|
+
validateObject(object, document.system, objectMap, groupIds, diagnostics);
|
|
45
|
+
}
|
|
46
|
+
for (const event of document.events) {
|
|
47
|
+
validateEvent(event, document.system, objectMap, diagnostics);
|
|
48
|
+
}
|
|
49
|
+
return diagnostics;
|
|
50
|
+
}
|
|
51
|
+
function validateRelation(relation, objectMap, diagnostics) {
|
|
52
|
+
if (!relation.from) {
|
|
53
|
+
diagnostics.push(error("validate.relation.from.required", `Relation "${relation.id}" is missing a "from" target.`));
|
|
54
|
+
}
|
|
55
|
+
else if (!objectMap.has(relation.from)) {
|
|
56
|
+
diagnostics.push(error("validate.relation.from.unknown", `Unknown relation source "${relation.from}" on "${relation.id}".`));
|
|
57
|
+
}
|
|
58
|
+
if (!relation.to) {
|
|
59
|
+
diagnostics.push(error("validate.relation.to.required", `Relation "${relation.id}" is missing a "to" target.`));
|
|
60
|
+
}
|
|
61
|
+
else if (!objectMap.has(relation.to)) {
|
|
62
|
+
diagnostics.push(error("validate.relation.to.unknown", `Unknown relation target "${relation.to}" on "${relation.id}".`));
|
|
63
|
+
}
|
|
64
|
+
if (!relation.kind) {
|
|
65
|
+
diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap) {
|
|
69
|
+
const filter = viewpoint.filter;
|
|
70
|
+
if (sourceSchemaVersion === "2.1" || sourceSchemaVersion === "2.5") {
|
|
71
|
+
if (filter) {
|
|
72
|
+
for (const groupId of filter.groupIds) {
|
|
73
|
+
if (!groupIds.has(groupId)) {
|
|
74
|
+
diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpoint.id}".`, undefined, `viewpoint.${viewpoint.id}.groups`));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const eventId of viewpoint.events ?? []) {
|
|
79
|
+
if (!eventIds.has(eventId)) {
|
|
80
|
+
diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpoint.id}".`, undefined, `viewpoint.${viewpoint.id}.events`));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
validateProjection(viewpoint.projection, diagnostics, `viewpoint.${viewpoint.id}.projection`, viewpoint.id);
|
|
85
|
+
validateCamera(viewpoint.camera, viewpoint.projection, viewpoint.rotationDeg, diagnostics, viewpoint.id, viewpoint.focusObjectId, viewpoint.selectedObjectId, filter, objectMap);
|
|
86
|
+
}
|
|
87
|
+
function validateObject(object, system, objectMap, groupIds, diagnostics) {
|
|
88
|
+
const placement = object.placement;
|
|
89
|
+
const orbitPlacement = placement?.mode === "orbit" ? placement : null;
|
|
90
|
+
const parentObject = placement?.mode === "orbit" ? objectMap.get(placement.target) ?? null : null;
|
|
91
|
+
if (object.groups) {
|
|
92
|
+
for (const groupId of object.groups) {
|
|
93
|
+
if (!groupIds.has(groupId)) {
|
|
94
|
+
diagnostics.push(warn("validate.group.unknown", `Unknown group "${groupId}" on "${object.id}".`, object.id, "groups"));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (typeof object.epoch === "string" && !object.epoch.trim()) {
|
|
99
|
+
diagnostics.push(warn("validate.epoch.empty", `Object "${object.id}" defines an empty epoch string.`, object.id, "epoch"));
|
|
100
|
+
}
|
|
101
|
+
if (typeof object.referencePlane === "string" && !object.referencePlane.trim()) {
|
|
102
|
+
diagnostics.push(warn("validate.referencePlane.empty", `Object "${object.id}" defines an empty reference plane string.`, object.id, "referencePlane"));
|
|
103
|
+
}
|
|
104
|
+
if (orbitPlacement) {
|
|
105
|
+
if (!objectMap.has(orbitPlacement.target)) {
|
|
106
|
+
diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
|
|
107
|
+
}
|
|
108
|
+
if (orbitPlacement.distance && orbitPlacement.semiMajor) {
|
|
109
|
+
diagnostics.push(error("validate.orbit.distanceConflict", `Object "${object.id}" cannot declare both "distance" and "semiMajor".`, object.id, "distance"));
|
|
110
|
+
}
|
|
111
|
+
if (orbitPlacement.phase && !object.epoch && !system?.epoch) {
|
|
112
|
+
diagnostics.push(warn("validate.phase.epochMissing", `Object "${object.id}" sets "phase" without an object or system epoch.`, object.id, "phase"));
|
|
113
|
+
}
|
|
114
|
+
if (orbitPlacement.inclination && !object.referencePlane && !system?.referencePlane) {
|
|
115
|
+
diagnostics.push(warn("validate.inclination.referencePlaneMissing", `Object "${object.id}" sets "inclination" without an object or system reference plane.`, object.id, "inclination"));
|
|
116
|
+
}
|
|
117
|
+
if (orbitPlacement.period && !massInSolar(parentObject?.properties.mass)) {
|
|
118
|
+
diagnostics.push(warn("validate.period.massMissing", `Object "${object.id}" sets "period" but its central mass cannot be derived.`, object.id, "period"));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (placement?.mode === "surface") {
|
|
122
|
+
const target = objectMap.get(placement.target);
|
|
123
|
+
if (!target) {
|
|
124
|
+
diagnostics.push(error("validate.surface.target.unknown", `Unknown placement target "${placement.target}" on "${object.id}".`, object.id, "surface"));
|
|
125
|
+
}
|
|
126
|
+
else if (!SURFACE_TARGET_TYPES.has(target.type)) {
|
|
127
|
+
diagnostics.push(error("validate.surface.target.invalid", `Surface target "${placement.target}" on "${object.id}" is not surface-capable.`, object.id, "surface"));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (placement?.mode === "at") {
|
|
131
|
+
if (object.type !== "structure" && object.type !== "phenomenon") {
|
|
132
|
+
diagnostics.push(error("validate.at.objectType", `Only structures and phenomena may use "at" placement; found "${object.type}" on "${object.id}".`, object.id, "at"));
|
|
133
|
+
}
|
|
134
|
+
if (!validateAtTarget(object, objectMap, diagnostics)) {
|
|
135
|
+
diagnostics.push(error("validate.at.target.unknown", `Unknown at-reference target "${placement.target}" on "${object.id}".`, object.id, "at"));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (object.resonance) {
|
|
139
|
+
const target = objectMap.get(object.resonance.targetObjectId);
|
|
140
|
+
if (!target) {
|
|
141
|
+
diagnostics.push(error("validate.resonance.target.unknown", `Unknown resonance target "${object.resonance.targetObjectId}" on "${object.id}".`, object.id, "resonance"));
|
|
142
|
+
}
|
|
143
|
+
else if (object.placement?.mode !== "orbit" ||
|
|
144
|
+
target.placement?.mode !== "orbit" ||
|
|
145
|
+
object.placement.target !== target.placement.target) {
|
|
146
|
+
diagnostics.push(warn("validate.resonance.orbitMismatch", `Resonance target "${object.resonance.targetObjectId}" on "${object.id}" does not share a compatible orbital parent.`, object.id, "resonance"));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
for (const rule of object.deriveRules ?? []) {
|
|
150
|
+
if (rule.field !== "period" || rule.strategy !== "kepler") {
|
|
151
|
+
diagnostics.push(warn("validate.derive.unsupported", `Unsupported derive rule "${rule.field} ${rule.strategy}" on "${object.id}".`, object.id, "derive"));
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const derivedPeriodDays = keplerPeriodDays(object, parentObject);
|
|
155
|
+
if (derivedPeriodDays === null) {
|
|
156
|
+
diagnostics.push(warn("validate.derive.inputsMissing", `Object "${object.id}" requests "derive period kepler" but lacks enough input data.`, object.id, "derive"));
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (!orbitPlacement?.period) {
|
|
160
|
+
diagnostics.push(info("validate.derive.period.available", `Object "${object.id}" can derive a Kepler period of ${formatDays(derivedPeriodDays)}.`, object.id, "derive"));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (const rule of object.validationRules ?? []) {
|
|
164
|
+
if (rule.rule !== "kepler") {
|
|
165
|
+
diagnostics.push(warn("validate.rule.unsupported", `Unsupported validation rule "${rule.rule}" on "${object.id}".`, object.id, "validate"));
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const actualPeriodDays = durationInDays(orbitPlacement?.period);
|
|
169
|
+
const derivedPeriodDays = keplerPeriodDays(object, parentObject);
|
|
170
|
+
if (actualPeriodDays === null || derivedPeriodDays === null) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const toleranceDays = toleranceForField(object, "period");
|
|
174
|
+
if (Math.abs(actualPeriodDays - derivedPeriodDays) > toleranceDays) {
|
|
175
|
+
diagnostics.push(error("validate.kepler.mismatch", `Object "${object.id}" fails Kepler validation for "period".`, object.id, "validate"));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function validateEvent(event, system, objectMap, diagnostics) {
|
|
180
|
+
const fieldPrefix = `event.${event.id}`;
|
|
181
|
+
const referencedIds = new Set();
|
|
182
|
+
if (!event.kind.trim()) {
|
|
183
|
+
diagnostics.push(error("validate.event.kind.required", `Event "${event.id}" is missing a "kind" value.`, undefined, `${fieldPrefix}.kind`));
|
|
184
|
+
}
|
|
185
|
+
if (typeof event.epoch === "string" && !event.epoch.trim()) {
|
|
186
|
+
diagnostics.push(warn("validate.event.epoch.empty", `Event "${event.id}" defines an empty epoch string.`, undefined, `${fieldPrefix}.epoch`));
|
|
187
|
+
}
|
|
188
|
+
if (typeof event.referencePlane === "string" && !event.referencePlane.trim()) {
|
|
189
|
+
diagnostics.push(warn("validate.event.referencePlane.empty", `Event "${event.id}" defines an empty reference plane string.`, undefined, `${fieldPrefix}.referencePlane`));
|
|
190
|
+
}
|
|
191
|
+
if (!event.targetObjectId && event.participantObjectIds.length === 0) {
|
|
192
|
+
diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, undefined, `${fieldPrefix}.participants`));
|
|
193
|
+
}
|
|
194
|
+
if (event.targetObjectId) {
|
|
195
|
+
referencedIds.add(event.targetObjectId);
|
|
196
|
+
if (!objectMap.has(event.targetObjectId)) {
|
|
197
|
+
diagnostics.push(error("validate.event.target.unknown", `Unknown event target "${event.targetObjectId}" on "${event.id}".`, undefined, `${fieldPrefix}.target`));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const seenParticipants = new Set();
|
|
201
|
+
for (const participantId of event.participantObjectIds) {
|
|
202
|
+
referencedIds.add(participantId);
|
|
203
|
+
if (seenParticipants.has(participantId)) {
|
|
204
|
+
diagnostics.push(warn("validate.event.participants.duplicate", `Event "${event.id}" repeats participant "${participantId}".`, undefined, `${fieldPrefix}.participants`));
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
seenParticipants.add(participantId);
|
|
208
|
+
if (!objectMap.has(participantId)) {
|
|
209
|
+
diagnostics.push(error("validate.event.participants.unknown", `Unknown event participant "${participantId}" on "${event.id}".`, undefined, `${fieldPrefix}.participants`));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (event.targetObjectId &&
|
|
213
|
+
event.participantObjectIds.length > 0 &&
|
|
214
|
+
!event.participantObjectIds.includes(event.targetObjectId)) {
|
|
215
|
+
diagnostics.push(warn("validate.event.target.notParticipant", `Event "${event.id}" defines a target outside its participants list.`, undefined, `${fieldPrefix}.target`));
|
|
216
|
+
}
|
|
217
|
+
if (event.positions.length === 0) {
|
|
218
|
+
diagnostics.push(warn("validate.event.positions.missing", `Event "${event.id}" has no positions block and cannot drive a scene snapshot.`, undefined, `${fieldPrefix}.positions`));
|
|
219
|
+
}
|
|
220
|
+
if (/(?:^|[-_])(solar-eclipse|lunar-eclipse|transit|occultation)(?:$|[-_])/.test(event.kind) && referencedIds.size < 3) {
|
|
221
|
+
diagnostics.push(warn("validate.event.kind.participants", `Event "${event.id}" looks like an eclipse or transit but references fewer than three bodies.`, undefined, `${fieldPrefix}.participants`));
|
|
222
|
+
}
|
|
223
|
+
const poseIds = new Set();
|
|
224
|
+
for (const pose of event.positions) {
|
|
225
|
+
const poseFieldPrefix = `${fieldPrefix}.pose.${pose.objectId}`;
|
|
226
|
+
if (poseIds.has(pose.objectId)) {
|
|
227
|
+
diagnostics.push(error("validate.event.pose.duplicate", `Event "${event.id}" defines "${pose.objectId}" more than once in positions.`, undefined, poseFieldPrefix));
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
poseIds.add(pose.objectId);
|
|
231
|
+
const object = objectMap.get(pose.objectId);
|
|
232
|
+
if (!object) {
|
|
233
|
+
diagnostics.push(error("validate.event.pose.object.unknown", `Unknown event pose object "${pose.objectId}" on "${event.id}".`, undefined, poseFieldPrefix));
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (!referencedIds.has(pose.objectId)) {
|
|
237
|
+
diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, undefined, poseFieldPrefix));
|
|
238
|
+
}
|
|
239
|
+
validateEventPose(pose, object, event, system, objectMap, diagnostics, poseFieldPrefix, event.id);
|
|
240
|
+
}
|
|
241
|
+
const missingPoseIds = [...referencedIds].filter((objectId) => !poseIds.has(objectId));
|
|
242
|
+
if (event.positions.length > 0 && missingPoseIds.length > 0) {
|
|
243
|
+
diagnostics.push(warn("validate.event.positions.partial", `Event "${event.id}" leaves ${missingPoseIds.length} referenced object(s) on their base placement.`, undefined, `${fieldPrefix}.positions`));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function validateEventPose(pose, object, event, system, objectMap, diagnostics, fieldPrefix, eventId) {
|
|
247
|
+
const placement = pose.placement;
|
|
248
|
+
if (!placement) {
|
|
249
|
+
diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, undefined, fieldPrefix));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (placement.mode === "orbit") {
|
|
253
|
+
if (!objectMap.has(placement.target)) {
|
|
254
|
+
diagnostics.push(error("validate.event.pose.orbit.target.unknown", `Unknown event orbit target "${placement.target}" on "${eventId}:${pose.objectId}".`, undefined, `${fieldPrefix}.orbit`));
|
|
255
|
+
}
|
|
256
|
+
if (placement.distance && placement.semiMajor) {
|
|
257
|
+
diagnostics.push(error("validate.event.pose.orbit.distanceConflict", `Event "${eventId}" pose "${pose.objectId}" cannot declare both "distance" and "semiMajor".`, undefined, `${fieldPrefix}.distance`));
|
|
258
|
+
}
|
|
259
|
+
if (placement.phase && !resolveEffectiveEpoch(system, object, event, pose)) {
|
|
260
|
+
diagnostics.push(warn("validate.event.pose.phase.epochMissing", `Event "${eventId}" pose "${pose.objectId}" sets "phase" without an effective epoch.`, undefined, `${fieldPrefix}.phase`));
|
|
261
|
+
}
|
|
262
|
+
if (placement.inclination && !resolveEffectiveReferencePlane(system, object, event, pose)) {
|
|
263
|
+
diagnostics.push(warn("validate.event.pose.inclination.referencePlaneMissing", `Event "${eventId}" pose "${pose.objectId}" sets "inclination" without an effective reference plane.`, undefined, `${fieldPrefix}.inclination`));
|
|
264
|
+
}
|
|
265
|
+
if (placement.period && !massInSolar(objectMap.get(placement.target)?.properties.mass)) {
|
|
266
|
+
diagnostics.push(warn("validate.event.pose.period.massMissing", `Event "${eventId}" pose "${pose.objectId}" sets "period" but its central mass cannot be derived.`, undefined, `${fieldPrefix}.period`));
|
|
267
|
+
}
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (placement.mode === "surface") {
|
|
271
|
+
const target = objectMap.get(placement.target);
|
|
272
|
+
if (!target) {
|
|
273
|
+
diagnostics.push(error("validate.event.pose.surface.target.unknown", `Unknown event surface target "${placement.target}" on "${eventId}:${pose.objectId}".`, undefined, `${fieldPrefix}.surface`));
|
|
274
|
+
}
|
|
275
|
+
else if (!SURFACE_TARGET_TYPES.has(target.type)) {
|
|
276
|
+
diagnostics.push(error("validate.event.pose.surface.target.invalid", `Event surface target "${placement.target}" on "${eventId}:${pose.objectId}" is not surface-capable.`, undefined, `${fieldPrefix}.surface`));
|
|
277
|
+
}
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (placement.mode === "at") {
|
|
281
|
+
if (object.type !== "structure" && object.type !== "phenomenon") {
|
|
282
|
+
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}".`, undefined, `${fieldPrefix}.at`));
|
|
283
|
+
}
|
|
284
|
+
const reference = placement.reference;
|
|
285
|
+
if (reference.kind === "named" && !objectMap.has(reference.name)) {
|
|
286
|
+
diagnostics.push(error("validate.event.pose.at.target.unknown", `Unknown event at-reference target "${placement.target}" on "${eventId}:${pose.objectId}".`, undefined, `${fieldPrefix}.at`));
|
|
287
|
+
}
|
|
288
|
+
else if (reference.kind === "anchor" && !objectMap.has(reference.objectId)) {
|
|
289
|
+
diagnostics.push(error("validate.event.pose.anchor.target.unknown", `Unknown event anchor target "${reference.objectId}" on "${eventId}:${pose.objectId}".`, undefined, `${fieldPrefix}.at`));
|
|
290
|
+
}
|
|
291
|
+
else if (reference.kind === "lagrange") {
|
|
292
|
+
if (!objectMap.has(reference.primary)) {
|
|
293
|
+
diagnostics.push(error("validate.event.pose.lagrange.primary.unknown", `Unknown event Lagrange target "${reference.primary}" on "${eventId}:${pose.objectId}".`, undefined, `${fieldPrefix}.at`));
|
|
294
|
+
}
|
|
295
|
+
else if (reference.secondary && !objectMap.has(reference.secondary)) {
|
|
296
|
+
diagnostics.push(error("validate.event.pose.lagrange.secondary.unknown", `Unknown event Lagrange target "${reference.secondary}" on "${eventId}:${pose.objectId}".`, undefined, `${fieldPrefix}.at`));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function validateAtTarget(object, objectMap, diagnostics) {
|
|
302
|
+
const reference = object.placement?.mode === "at" ? object.placement.reference : null;
|
|
303
|
+
if (!reference) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
if (reference.kind === "named") {
|
|
307
|
+
return objectMap.has(reference.name);
|
|
308
|
+
}
|
|
309
|
+
if (reference.kind === "anchor") {
|
|
310
|
+
if (!objectMap.has(reference.objectId)) {
|
|
311
|
+
diagnostics.push(error("validate.anchor.target.unknown", `Unknown anchor target "${reference.objectId}" on "${object.id}".`, object.id, "at"));
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
if (!objectMap.has(reference.primary)) {
|
|
317
|
+
diagnostics.push(error("validate.lagrange.primary.unknown", `Unknown Lagrange reference "${reference.primary}" on "${object.id}".`, object.id, "at"));
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
if (reference.secondary && !objectMap.has(reference.secondary)) {
|
|
321
|
+
diagnostics.push(error("validate.lagrange.secondary.unknown", `Unknown Lagrange reference "${reference.secondary}" on "${object.id}".`, object.id, "at"));
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
function keplerPeriodDays(object, parentObject) {
|
|
327
|
+
const placement = object.placement;
|
|
328
|
+
if (!placement || placement.mode !== "orbit") {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
const semiMajorAu = distanceInAu(placement.semiMajor ?? placement.distance);
|
|
332
|
+
const centralMassSolar = massInSolar(parentObject?.properties.mass);
|
|
333
|
+
if (semiMajorAu === null || centralMassSolar === null || centralMassSolar <= 0) {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
const periodYears = Math.sqrt((semiMajorAu ** 3) / centralMassSolar);
|
|
337
|
+
return periodYears * 365.25;
|
|
338
|
+
}
|
|
339
|
+
function distanceInAu(value) {
|
|
340
|
+
if (!value)
|
|
341
|
+
return null;
|
|
342
|
+
switch (value.unit) {
|
|
343
|
+
case null:
|
|
344
|
+
case "au":
|
|
345
|
+
return value.value;
|
|
346
|
+
case "km":
|
|
347
|
+
return value.value / AU_IN_KM;
|
|
348
|
+
case "m":
|
|
349
|
+
return value.value / (AU_IN_KM * 1000);
|
|
350
|
+
case "ly":
|
|
351
|
+
return value.value * LY_IN_AU;
|
|
352
|
+
case "pc":
|
|
353
|
+
return value.value * PC_IN_AU;
|
|
354
|
+
case "kpc":
|
|
355
|
+
return value.value * KPC_IN_AU;
|
|
356
|
+
case "re":
|
|
357
|
+
return (value.value * EARTH_RADIUS_IN_KM) / AU_IN_KM;
|
|
358
|
+
case "sol":
|
|
359
|
+
return (value.value * SOLAR_RADIUS_IN_KM) / AU_IN_KM;
|
|
360
|
+
default:
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function massInSolar(value) {
|
|
365
|
+
if (!value || typeof value !== "object" || !("value" in value)) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
const unitValue = value;
|
|
369
|
+
switch (unitValue.unit) {
|
|
370
|
+
case null:
|
|
371
|
+
case "sol":
|
|
372
|
+
return unitValue.value;
|
|
373
|
+
case "me":
|
|
374
|
+
return unitValue.value / EARTH_MASSES_PER_SOLAR;
|
|
375
|
+
case "mj":
|
|
376
|
+
return unitValue.value / JUPITER_MASSES_PER_SOLAR;
|
|
377
|
+
default:
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function durationInDays(value) {
|
|
382
|
+
if (!value)
|
|
383
|
+
return null;
|
|
384
|
+
switch (value.unit) {
|
|
385
|
+
case null:
|
|
386
|
+
case "d":
|
|
387
|
+
return value.value;
|
|
388
|
+
case "s":
|
|
389
|
+
return value.value / 86_400;
|
|
390
|
+
case "min":
|
|
391
|
+
return value.value / 1_440;
|
|
392
|
+
case "h":
|
|
393
|
+
return value.value / 24;
|
|
394
|
+
case "y":
|
|
395
|
+
return value.value * 365.25;
|
|
396
|
+
case "ky":
|
|
397
|
+
return value.value * 365_250;
|
|
398
|
+
case "my":
|
|
399
|
+
return value.value * 365_250_000;
|
|
400
|
+
case "gy":
|
|
401
|
+
return value.value * 365_250_000_000;
|
|
402
|
+
default:
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
function validateProjection(projection, diagnostics, field, viewpointId) {
|
|
407
|
+
if (projection !== "topdown" &&
|
|
408
|
+
projection !== "isometric" &&
|
|
409
|
+
projection !== "orthographic" &&
|
|
410
|
+
projection !== "perspective") {
|
|
411
|
+
diagnostics.push(error("validate.viewpoint.projection.invalid", `Unknown projection "${String(projection)}" in viewpoint "${viewpointId}".`, undefined, field));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function validateCamera(camera, projection, rotationDeg, diagnostics, viewpointId, focusObjectId, selectedObjectId, filter, objectMap) {
|
|
415
|
+
if (!camera) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const prefix = `viewpoint.${viewpointId}.camera`;
|
|
419
|
+
for (const [key, value] of [
|
|
420
|
+
["azimuth", camera.azimuth],
|
|
421
|
+
["elevation", camera.elevation],
|
|
422
|
+
["roll", camera.roll],
|
|
423
|
+
["distance", camera.distance],
|
|
424
|
+
]) {
|
|
425
|
+
if (value !== null && (!Number.isFinite(value) || (key === "distance" && value <= 0))) {
|
|
426
|
+
diagnostics.push(error("validate.viewpoint.camera.invalid", `Invalid camera ${key} "${String(value)}" in viewpoint "${viewpointId}".`, undefined, `${prefix}.${key}`));
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (camera.distance !== null && projection !== "perspective") {
|
|
430
|
+
diagnostics.push(warn("validate.viewpoint.camera.distance.partialEffect", `Camera "distance" only has a semantic effect in perspective viewpoints; "${viewpointId}" uses "${projection}".`, undefined, `${prefix}.distance`));
|
|
431
|
+
}
|
|
432
|
+
if (projection === "topdown" &&
|
|
433
|
+
(camera.elevation !== null || camera.roll !== null)) {
|
|
434
|
+
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.`, undefined, prefix));
|
|
435
|
+
}
|
|
436
|
+
if (projection === "isometric" &&
|
|
437
|
+
camera.elevation !== null) {
|
|
438
|
+
diagnostics.push(info("validate.viewpoint.camera.isometricStored", `Camera elevation on isometric viewpoint "${viewpointId}" is preserved semantically for future 3D rendering.`, undefined, `${prefix}.elevation`));
|
|
439
|
+
}
|
|
440
|
+
if (camera.azimuth !== null && camera.azimuth !== 0 && rotationDeg !== 0) {
|
|
441
|
+
diagnostics.push(warn("validate.viewpoint.rotation.cameraOverlap", `Viewpoint "${viewpointId}" uses camera.azimuth; keep "rotation" only for 2D screen rotation to avoid ambiguity.`, undefined, `${prefix}.azimuth`));
|
|
442
|
+
}
|
|
443
|
+
const hasAnchor = (focusObjectId !== null && objectMap.has(focusObjectId)) ||
|
|
444
|
+
(selectedObjectId !== null && objectMap.has(selectedObjectId)) ||
|
|
445
|
+
!!filter;
|
|
446
|
+
if (!hasAnchor) {
|
|
447
|
+
diagnostics.push(info("validate.viewpoint.camera.anchorMissing", `Viewpoint "${viewpointId}" stores camera settings without a focus object, selection, or filter anchor.`, undefined, prefix));
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
function resolveEffectiveEpoch(system, object, event, pose) {
|
|
451
|
+
return normalizeOptionalContextString(pose?.epoch) ??
|
|
452
|
+
normalizeOptionalContextString(event?.epoch) ??
|
|
453
|
+
normalizeOptionalContextString(object.epoch) ??
|
|
454
|
+
normalizeOptionalContextString(system?.epoch) ??
|
|
455
|
+
null;
|
|
456
|
+
}
|
|
457
|
+
function resolveEffectiveReferencePlane(system, object, event, pose) {
|
|
458
|
+
return normalizeOptionalContextString(pose?.referencePlane) ??
|
|
459
|
+
normalizeOptionalContextString(event?.referencePlane) ??
|
|
460
|
+
normalizeOptionalContextString(object.referencePlane) ??
|
|
461
|
+
normalizeOptionalContextString(system?.referencePlane) ??
|
|
462
|
+
null;
|
|
463
|
+
}
|
|
464
|
+
function normalizeOptionalContextString(value) {
|
|
465
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
466
|
+
}
|
|
467
|
+
function toleranceForField(object, field) {
|
|
468
|
+
const tolerance = object.tolerances?.find((entry) => entry.field === field)?.value;
|
|
469
|
+
if (typeof tolerance === "number") {
|
|
470
|
+
return tolerance;
|
|
471
|
+
}
|
|
472
|
+
if (tolerance && typeof tolerance === "object" && "value" in tolerance) {
|
|
473
|
+
return durationInDays(tolerance) ?? 0;
|
|
474
|
+
}
|
|
475
|
+
return 0;
|
|
476
|
+
}
|
|
477
|
+
function formatDays(days) {
|
|
478
|
+
return `${Math.round(days * 100) / 100}d`;
|
|
479
|
+
}
|
|
480
|
+
function error(code, message, objectId, field) {
|
|
481
|
+
return { code, severity: "error", source: "validate", message, objectId, field };
|
|
482
|
+
}
|
|
483
|
+
function warn(code, message, objectId, field) {
|
|
484
|
+
return { code, severity: "warning", source: "validate", message, objectId, field };
|
|
485
|
+
}
|
|
486
|
+
function info(code, message, objectId, field) {
|
|
487
|
+
return { code, severity: "info", source: "validate", message, objectId, field };
|
|
488
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AstDocument, DiagnosticResult, WorldOrbitDiagnostic, WorldOrbitDiagnosticSource, WorldOrbitDocument } from "./types.js";
|
|
2
|
+
export interface ParsedWorldOrbitDocument {
|
|
3
|
+
ast: AstDocument;
|
|
4
|
+
document: WorldOrbitDocument;
|
|
5
|
+
}
|
|
6
|
+
export declare function createDiagnostic(diagnostic: WorldOrbitDiagnostic): WorldOrbitDiagnostic;
|
|
7
|
+
export declare function diagnosticFromError(error: unknown, source: WorldOrbitDiagnosticSource, code?: string): WorldOrbitDiagnostic;
|
|
8
|
+
export declare function parseWithDiagnostics(source: string): DiagnosticResult<ParsedWorldOrbitDocument>;
|
|
9
|
+
export declare function normalizeWithDiagnostics(ast: AstDocument): DiagnosticResult<WorldOrbitDocument>;
|
|
10
|
+
export declare function validateDocumentWithDiagnostics(document: WorldOrbitDocument): DiagnosticResult<WorldOrbitDocument>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { WorldOrbitError } from "./errors.js";
|
|
2
|
+
import { normalizeDocument } from "./normalize.js";
|
|
3
|
+
import { parseWorldOrbit } from "./parse.js";
|
|
4
|
+
import { validateDocument } from "./validate.js";
|
|
5
|
+
export function createDiagnostic(diagnostic) {
|
|
6
|
+
return { ...diagnostic };
|
|
7
|
+
}
|
|
8
|
+
export function diagnosticFromError(error, source, code = `${source}.failed`) {
|
|
9
|
+
if (error instanceof WorldOrbitError) {
|
|
10
|
+
return {
|
|
11
|
+
code,
|
|
12
|
+
severity: "error",
|
|
13
|
+
source,
|
|
14
|
+
message: error.message,
|
|
15
|
+
line: error.line,
|
|
16
|
+
column: error.column,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (error instanceof Error) {
|
|
20
|
+
return {
|
|
21
|
+
code,
|
|
22
|
+
severity: "error",
|
|
23
|
+
source,
|
|
24
|
+
message: error.message,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
code,
|
|
29
|
+
severity: "error",
|
|
30
|
+
source,
|
|
31
|
+
message: String(error),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function parseWithDiagnostics(source) {
|
|
35
|
+
let ast;
|
|
36
|
+
try {
|
|
37
|
+
ast = parseWorldOrbit(source);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
const diagnostic = diagnosticFromError(error, "parse");
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
value: null,
|
|
44
|
+
diagnostics: [diagnostic],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
let document;
|
|
48
|
+
try {
|
|
49
|
+
document = normalizeDocument(ast);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
value: null,
|
|
55
|
+
diagnostics: [diagnosticFromError(error, "normalize")],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
validateDocument(document);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
value: null,
|
|
65
|
+
diagnostics: [diagnosticFromError(error, "validate")],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
ok: true,
|
|
70
|
+
value: {
|
|
71
|
+
ast,
|
|
72
|
+
document,
|
|
73
|
+
},
|
|
74
|
+
diagnostics: [],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function normalizeWithDiagnostics(ast) {
|
|
78
|
+
try {
|
|
79
|
+
return {
|
|
80
|
+
ok: true,
|
|
81
|
+
value: normalizeDocument(ast),
|
|
82
|
+
diagnostics: [],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
value: null,
|
|
89
|
+
diagnostics: [diagnosticFromError(error, "normalize")],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function validateDocumentWithDiagnostics(document) {
|
|
94
|
+
try {
|
|
95
|
+
validateDocument(document);
|
|
96
|
+
return {
|
|
97
|
+
ok: true,
|
|
98
|
+
value: document,
|
|
99
|
+
diagnostics: [],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
return {
|
|
104
|
+
ok: false,
|
|
105
|
+
value: null,
|
|
106
|
+
diagnostics: [diagnosticFromError(error, "validate")],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|