@rr0/ufoathome 0.53.0 → 0.54.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.
package/README.md CHANGED
@@ -8,23 +8,23 @@ recommendation](https://rr0.org/time/1/9/6/8/07/29/Symposium/Shepard/index_fr.ht
8
8
  testimony is more faithful than an oral or written one.
9
9
 
10
10
  Originally a Java applet (2003), the project has been rewritten from scratch in TypeScript: a small,
11
- dependency-light engine (keyframe timeline, recording, playback, Canvas2D rendering) wrapped in four vanilla
11
+ dependency-light engine (keyframe timeline, recording, playback, a three.js scene) wrapped in three vanilla
12
12
  [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) — no UI framework, no build step
13
- required by the consuming page. Two of the four (`<rr0-scene>`, and `<rr0-sighting>` which always composes it) pull
14
- in [Three.js](https://threejs.org/) for the 3D backdrop — see [`<rr0-scene>`](#rr0-scene--3d-decor) below for why
15
- that's an isolated, opt-in bundle rather than a project-wide dependency.
13
+ required by the consuming page. All three pull in [Three.js](https://threejs.org/): the phenomenon itself stands in
14
+ the scene, so there is no lighter, sky-less variant — see [`<rr0-scene>`](#rr0-scene--the-scene-and-playback) below
15
+ for what that buys.
16
16
 
17
17
  ### Naming
18
18
 
19
- `<rr0-ufo>` is the UFO's own 2D shape/appearance/movement layer no "player" suffix, since read-only playback is
20
- its default behavior and `<rr0-sighting-editor>` is the one that needs a qualifier (it *adds* recording on top).
21
- `<rr0-scene>` is named without "ufo" on purpose: it only renders a generic 3D decor (sky/horizon/stars) from a
22
- real-world time and place, with no UFO-specific logic of its own today it composes a nested `<rr0-ufo>` for the
23
- common case (see its section below), but the decor itself could back other kinds of reconstructions later. A fully
24
- generic version (accepting arbitrary overlay content instead of always creating its own `<rr0-ufo>`) is a natural
25
- follow-up, not implemented yet. `<rr0-sighting>` (renamed from `<rr0-ufo-witnesses>` — see below) is the standard
26
- way to display any real sighting, whether it has one witness or several: a witness account always implies a real
27
- place and time, so it always composes `<rr0-scene>`, never a bare `<rr0-ufo>`.
19
+ `<rr0-scene>` is named without "ufo" on purpose: it renders a generic 3D scene (sky/horizon/stars/decor) from a
20
+ real-world time and place, and stands the witness's own phenomenon in it. Under it lives a playback layer —
21
+ `UfoElement`, the timeline, the controls, the canvas the pointer works on reached as `scene.ufoElement`; it was a
22
+ component of its own (`<rr0-ufo>`, the shape painted on a bare background) until 0.54.0, when the phenomenon moved
23
+ into the scene and a shape with no sky stopped being a thing this project draws. Read-only playback needs no
24
+ "player" suffix, since it is every component's default behavior, and `<rr0-sighting-editor>` is the one that needs
25
+ a qualifier (it *adds* recording on top). `<rr0-sighting>` (renamed from `<rr0-ufo-witnesses>` — see below) is the
26
+ standard way to display any real sighting, whether it has one witness or several: a witness account always implies
27
+ a real place and time, so it always composes `<rr0-scene>`.
28
28
 
29
29
  The project's own site is **[ufoathome.org](https://ufoathome.org)**: [the demos](https://ufoathome.org/demos/)
30
30
  (every reconstruction running side by side, including the same sighting through three different instruments),
@@ -39,12 +39,11 @@ whole recording file and hands out the two lines that embed one. See the
39
39
  npm install @rr0/ufoathome
40
40
  ```
41
41
 
42
- Four self-contained, pre-built ES modules are published — each self-registers its custom element as soon as it's
42
+ Three self-contained, pre-built ES modules are published — each self-registers its custom element as soon as it's
43
43
  imported, no explicit setup call needed:
44
44
 
45
45
  ```html
46
- <script type="module" src="/node_modules/@rr0/ufoathome/dist-embed-ufo/rr0-ufo.mjs"></script>
47
- <script type="module" src="/node_modules/@rr0/ufoathome/dist-embed/rr0-sighting-editor.mjs"></script>
46
+ <script type="module" src="/node_modules/@rr0/ufoathome/dist-embed-sighting-editor/rr0-sighting-editor.mjs"></script>
48
47
  <script type="module" src="/node_modules/@rr0/ufoathome/dist-embed-scene/rr0-scene.mjs"></script>
49
48
  <script type="module" src="/node_modules/@rr0/ufoathome/dist-embed-sighting/rr0-sighting.mjs"></script>
50
49
  ```
@@ -52,25 +51,80 @@ imported, no explicit setup call needed:
52
51
  or, from a bundler:
53
52
 
54
53
  ```ts
55
- import "@rr0/ufoathome/ufo" // registers <rr0-ufo>
56
54
  import "@rr0/ufoathome/editor" // registers <rr0-sighting-editor> (and <rr0-scene>, which it composes)
57
- import "@rr0/ufoathome/scene" // registers <rr0-scene> (and <rr0-ufo>, which it composes)
55
+ import "@rr0/ufoathome/scene" // registers <rr0-scene>
58
56
  import "@rr0/ufoathome/sighting" // registers <rr0-sighting> (and <rr0-scene>, which it composes)
59
57
  ```
60
58
 
61
- Only load the one(s) a given page actually needs `rr0-scene.mjs` and `rr0-sighting.mjs` in particular pull in
62
- Three.js and are far heavier than the other two (see their sections below), so pages that just need playback of an
63
- already-drawn shape with no astronomy backdrop should stick to `rr0-ufo.mjs`.
59
+ Only load the one a given page actually needs: each is self-contained and each carries Three.js and a star
60
+ catalogue, which is what a real sky costs. A page that loaded the former `rr0-ufo.mjs` still works ufoathome.org
61
+ forwards that address to `rr0-scene.mjs`, whose bundle registers the tag as its own inner layer.
64
62
 
65
- ## `<rr0-ufo>` — read-only playback
63
+ ## `<rr0-sighting-editor>` — full editor
66
64
 
67
- The lightweight component (~9KB): a canvas plus Play/Pause/Loop/seek controls. Use it wherever a page only needs to
68
- *replay* an already-recorded sighting this is the one to embed in content pages.
65
+ The authoring component (~540KB gzip see below for why): everything `<rr0-scene>` has, plus a shape/appearance
66
+ toolbar (oval/polygon presets, color, transparency, halo, and the object's real reported
67
+ size/distance — see [Apparent size](#apparent-size)) and drag-to-record. It composes a nested
68
+ `<rr0-scene>` internally, so the shape being drawn is always seen against the
69
+ sighting's own real sky, computed live from whatever latitude/longitude/heading/orientation/observation-time
70
+ fields the toolbar currently holds (see [Architecture](#architecture)). This absorbs `<rr0-scene>`'s own
71
+ Three.js/`astronomy-engine` weight on top of the authoring-only code this element already carried (Recorder
72
+ engine, SamplingClock, appearance toolbar) — a page that only needs to *play* a sighting (the common case: an
73
+ rr0.org case dossier) should embed `<rr0-sighting>` (or `<rr0-scene>` alone) directly, never
74
+ this heavier authoring component.
69
75
 
70
76
  ```html
71
- <rr0-ufo src="sighting.json"></rr0-ufo>
77
+ <rr0-sighting-editor></rr0-sighting-editor>
78
+ <rr0-sighting-editor src="sighting.json"></rr0-sighting-editor>
72
79
  ```
73
80
 
81
+ With `src`, the editor opens on an existing recording instead of an empty canvas — the same
82
+ attribute the three other elements take. That is what makes an address per observation possible:
83
+ [ufoathome.org's editor](https://ufoathome.org/editor/) maps its own `?sighting=` parameter onto
84
+ this attribute, and [its player](https://ufoathome.org/player/) does the same for read-only replay.
85
+ Any path the site does not otherwise serve becomes that parameter, so
86
+
87
+ - `https://ufoathome.org/player/?sighting=/demo-data/witness-socorro.json`, or simply
88
+ - `https://ufoathome.org/Socorro`
89
+
90
+ open that observation. A bare name with no `/` is looked for among the site's own demos first, then
91
+ as an rr0.org case directory — the shape the links that predate that site were written in, kept
92
+ working. Either page also takes a full address of your own; the editor additionally has a
93
+ **Load from URL** field, which is an explicit gesture by whoever is sitting at the keyboard.
94
+
95
+ Usage: click **Record**, move the pointer over the canvas to draw the UFO's path, click **Stop**, then **Play** to
96
+ replay it. The playback layer's `enableClickToPlay` is set to `false` here — a completed recording drag also
97
+ fires a native "click" on the canvas, which would otherwise spuriously toggle playback right after recording.
98
+
99
+ All of the toolbar's own labels (shape presets, Color/Transparency/Halo, Add shape, Record/Stop, Export,
100
+ Duration) are translated (English/French) the same way the playback layer's own labels are — based on
101
+ the host page's own `lang` then `navigator.languages`, no picker UI.
102
+
103
+ | Member | Kind | Description |
104
+ |---|---|---|
105
+ | `src` | attribute | URL of a `SightingRecordingJson` to open in the editor, fetched on connect and whenever the attribute changes |
106
+ | `sightingData` | property (get/set) | Delegates to the nested scene's `sightingData` |
107
+ | `appearance` | property (get/set, accepts a partial object on set) | `{ presetId: "oval" \| "polygon", color: string, transparency: number, haloScale: number }` — the UFO's appearance used for the next recording |
108
+
109
+ ## `<rr0-scene>` — the scene, and playback
110
+
111
+ The element the two others build on (~530KB gzip — [Three.js](https://threejs.org/) plus
112
+ [`astronomy-engine`](https://github.com/cosinekitty/astronomy)'s planetary/lunar position tables, which don't
113
+ tree-shake since they're one shared data table used internally for every body): the witness's own phenomenon
114
+ standing in a 3D sky/horizon/starfield/decor scene computed from the recording's real time and place, with the
115
+ playback controls under it. Its own members are `src`, `sightingData`, `loadFromSrc`, `enableClickToPlay` (forwarded
116
+ to the playback layer), `ufoElement` (that layer), `sceneRenderer`, and the attributes `show-compass`,
117
+ `show-witness-map` and `hide-milestones`. Click-to-play/pause works anywhere on the scene (the playback layer's
118
+ transparent canvas covers the whole stage), and the fullscreen button fullscreens the *whole* scene — it sets the
119
+ layer's `fullscreenTarget` to its own outer stage for this.
120
+
121
+ ### Playback, on `ufoElement`
122
+
123
+ Everything about replaying a recording lives one property down, on the playback layer every component composes —
124
+ `scene.ufoElement.play()`, and `sighting.scene.ufoElement.play()` from the outermost. The layer is the former
125
+ `<rr0-ufo>` (see [Naming](#naming)): the timeline, the controls, the seek bar and the canvas the pointer works on,
126
+ which since 0.54.0 draws nothing but the editing handles — the shape itself stands in the scene.
127
+
74
128
  | Member | Kind | Description |
75
129
  |---|---|---|
76
130
  | `src` | attribute | URL of a [`SightingRecordingJson`](#data-format) file, fetched automatically on connect and whenever the attribute changes |
@@ -122,63 +176,6 @@ reader is reading it in, and a bilingual site that serves the same article at tw
122
176
  `navigator.languages` cannot know. A page that declares nothing falls through to the browser's list exactly as
123
177
  before.
124
178
 
125
- ## `<rr0-sighting-editor>` — full editor
126
-
127
- The authoring component (~540KB gzip — see below for why): everything `<rr0-ufo>` has, plus a shape/appearance
128
- toolbar (oval/polygon presets, color, transparency, halo, and the object's real reported
129
- size/distance — see [Apparent size](#apparent-size)) and drag-to-record. It composes a nested
130
- `<rr0-scene>` internally — not a bare `<rr0-ufo>` — so the shape being drawn is always seen against the
131
- sighting's own real sky, computed live from whatever latitude/longitude/heading/orientation/observation-time
132
- fields the toolbar currently holds (see [Architecture](#architecture)). This absorbs `<rr0-scene>`'s own
133
- Three.js/`astronomy-engine` weight on top of the authoring-only code this element already carried (Recorder
134
- engine, SamplingClock, appearance toolbar) — a page that only needs to *play* a sighting (the common case: an
135
- rr0.org case dossier) should still embed the much lighter `<rr0-ufo>` (or `<rr0-scene>` alone) directly, never
136
- this heavier authoring component.
137
-
138
- ```html
139
- <rr0-sighting-editor></rr0-sighting-editor>
140
- <rr0-sighting-editor src="sighting.json"></rr0-sighting-editor>
141
- ```
142
-
143
- With `src`, the editor opens on an existing recording instead of an empty canvas — the same
144
- attribute the three other elements take. That is what makes an address per observation possible:
145
- [ufoathome.org's editor](https://ufoathome.org/editor/) maps its own `?sighting=` parameter onto
146
- this attribute, and [its player](https://ufoathome.org/player/) does the same for read-only replay.
147
- Any path the site does not otherwise serve becomes that parameter, so
148
-
149
- - `https://ufoathome.org/player/?sighting=/demo-data/witness-socorro.json`, or simply
150
- - `https://ufoathome.org/Socorro`
151
-
152
- open that observation. A bare name with no `/` is looked for among the site's own demos first, then
153
- as an rr0.org case directory — the shape the links that predate that site were written in, kept
154
- working. Either page also takes a full address of your own; the editor additionally has a
155
- **Load from URL** field, which is an explicit gesture by whoever is sitting at the keyboard.
156
-
157
- Usage: click **Record**, move the pointer over the canvas to draw the UFO's path, click **Stop**, then **Play** to
158
- replay it. The nested `<rr0-ufo>`'s `enableClickToPlay` is set to `false` here — a completed recording drag also
159
- fires a native "click" on the canvas, which would otherwise spuriously toggle playback right after recording.
160
-
161
- All of the toolbar's own labels (shape presets, Color/Transparency/Halo, Add shape, Record/Stop, Export,
162
- Duration) are translated (English/French) the same way `<rr0-ufo>`'s own labels are — based on
163
- the host page's own `lang` then `navigator.languages`, no picker UI.
164
-
165
- | Member | Kind | Description |
166
- |---|---|---|
167
- | `src` | attribute | URL of a `SightingRecordingJson` to open in the editor, fetched on connect and whenever the attribute changes |
168
- | `sightingData` | property (get/set) | Delegates to the nested `<rr0-ufo>`'s `sightingData` |
169
- | `appearance` | property (get/set, accepts a partial object on set) | `{ presetId: "oval" \| "polygon", color: string, transparency: number, haloScale: number }` — the UFO's appearance used for the next recording |
170
-
171
- ## `<rr0-scene>` — 3D decor
172
-
173
- The environmental variant (~530KB gzip — [Three.js](https://threejs.org/) plus
174
- [`astronomy-engine`](https://github.com/cosinekitty/astronomy)'s planetary/lunar position tables, which don't
175
- tree-shake since they're one shared data table used internally for every body — this is by far the heaviest
176
- of the four bundles, load it only on pages that want it): everything `<rr0-ufo>` has, composited over a 3D
177
- sky/horizon/starfield backdrop instead of a plain background. Same markup and members as `<rr0-ufo>` (`src`,
178
- `sightingData`, `loadFromSrc`, `enableClickToPlay`) — it's a drop-in upgrade, including click-to-play/pause
179
- anywhere on the scene (the nested `<rr0-ufo>`'s transparent canvas covers the whole stage). The fullscreen button
180
- fullscreens the *whole* scene (3D backdrop included), not just the nested `<rr0-ufo>`'s own overlay — it sets the
181
- nested element's `fullscreenTarget` to its own outer stage for this.
182
179
 
183
180
  ```html
184
181
  <rr0-scene src="sighting.json"></rr0-scene>
@@ -327,15 +324,29 @@ apparitions, and the peak magnitudes and tail lengths recorded at the time, are
327
324
  `scripts/build-comet-catalog.ts` — the orbits are looked up, the brightness is an observation, and the script's
328
325
  own doc comment explains why the two cannot come from the same place. The generated file *is* checked in.
329
326
 
330
- The UFO shape itself deliberately stays a 2D overlay on top of the 3D decor, never "upgraded" to a 3D object: it's
331
- what the witness reported possibly a misidentification or optical effect not something to interpret as a real
332
- 3D shape. Only the surrounding environment, independently computable from real astronomy, is rendered in 3D.
327
+ **The phenomenon stands in the scene, and is still only what the witness saw.** The shape used to be painted on a
328
+ 2D canvas laid over the 3D scene, so that nothing about it could be read as a claim about a solid at a distance. It
329
+ is now a plane *in* the three.js scene (`src/render3d/PhenomenonSystem.ts`), facing the witness and scaled so that
330
+ it subtends exactly the angle the recording states — which makes it look the same from their eye at any distance
331
+ whatever, and is what keeps the claim where it was: the recording states angles and nothing else, and the distance
332
+ the plane is drawn at is a parameter of the picture (`src/engine/shape/PhenomenonDepth.ts`, see *Where the shape
333
+ is drawn* below). What the plane carries is the very picture the overlay painted — the same `CanvasRenderer` draws
334
+ the same halo, blur, veil and spikes into its texture. What changed is who decides what hides it: the decor's own
335
+ depth, per pixel, so a patrol car in front of it hides exactly the part of it a patrol car would, where the overlay
336
+ sampled nine points and hid the shape whole or not at all. The ground and the terrain are kept out of that on
337
+ purpose — the phenomena are drawn in a pass of their own, depth-tested against the decor alone — because a relief
338
+ patch at thirty-metre resolution deciding what a witness saw would not be a reconstruction (Socorro's craft, a
339
+ hundred feet away in the arroyo below the road, sank two metres under one). It also means the phenomenon goes
340
+ through the instrument's own projection like everything else in the scene, and through the same long-exposure
341
+ accumulation, instead of an approximation of each on a separate layer. The overlay keeps the pointer's business:
342
+ selection handles, outlines, hit-testing. A body in the round — an ovoid, a real model standing in for the shape
343
+ to test "was it a helicopter" — is a different statement, and a different object, for a recording that makes it.
333
344
 
334
345
  ## `<rr0-sighting>` — standard sighting view
335
346
 
336
347
  The standard way to display any real sighting, whether it has one witness or several — renamed from
337
348
  `<rr0-ufo-witnesses>` once it stopped being just a multi-witness selector (see [Naming](#naming)). It composes a
338
- nested `<rr0-scene>` (not a bare `<rr0-ufo>`) the same way `<rr0-sighting-editor>` does, since a witness recording is
349
+ nested `<rr0-scene>` the same way `<rr0-sighting-editor>` does, since a witness recording is
339
350
  always a real sighting and always needs the real sky/ground backdrop.
340
351
 
341
352
  ```html
@@ -344,7 +355,7 @@ always a real sighting and always needs the real sky/ground backdrop.
344
355
 
345
356
  `src` accepts either a single witness's `sighting.json` directly (the common case — no extra file needed) or, for
346
357
  a case with several witnesses, a small manifest: a plain JSON array of each witness's own `SightingRecordingJson`
347
- URL (typically relative to the case's own page, same as `<rr0-ufo>`'s own `src`):
358
+ URL (typically relative to the case's own page, same as `<rr0-scene>`'s own `src`):
348
359
 
349
360
  ```json
350
361
  ["chiles-sighting.json", "whitted-sighting.json"]
@@ -418,7 +429,7 @@ fold-outs on the right, both closed until asked for:
418
429
  resolved, plus the bundled thunder sound's own required attribution — see [`CREDITS.md`](CREDITS.md)).
419
430
 
420
431
  All of this component's own labels (Testimony by, About, Close, Observation/Date/Location/Case, Credits) are
421
- translated (English/French) the same way as `<rr0-ufo>`'s own labels.
432
+ translated (English/French) the same way as the playback layer's own labels.
422
433
 
423
434
  ## Data format
424
435
 
@@ -634,12 +645,13 @@ diff shows the weather and nothing else.
634
645
 
635
646
  `behindCloud` is how a recording says "it disappeared into a cloud" — keyframed like any other
636
647
  appearance field, and held rather than blended. It is *stated*, for the same reason
637
- `DecorObject.occludesSourceIds` is: this format describes a 2D appearance on the witness's own field
648
+ `DecorObject.occludesSourceIds` is: this format describes an appearance on the witness's own field
638
649
  of view, not where an object was in space, so nothing in it can deduce whether cloud came between
639
- them. A recording holds no distance at all (see *Apparent size* below), and the sky's own gaps are
640
- procedural noise leaving the question to geometry means tuning the weather until the reported
641
- disappearance happens to occur. So the witness's account is the whole answer: no `behindCloud`, no
642
- cloud.
650
+ them. A recording holds no distance at all (see *Apparent size* below) the distance a shape is
651
+ *drawn* at is a parameter of the picture, not a fact (see *Where the shape is drawn*) — and the
652
+ sky's own gaps are procedural noise: leaving the question to geometry means tuning the weather
653
+ until the reported disappearance happens to occur. So the witness's account is the whole answer: no
654
+ `behindCloud`, no cloud, and a shape declared behind cloud is not drawn while the deck is up.
643
655
 
644
656
  There used to be a geometric fallback here, for a recording that stated a real distance and made no
645
657
  claim about cloud. It went when stated distances did, and it had earned it: the one case it fired on
@@ -786,8 +798,9 @@ trading one distortion for another.)
786
798
 
787
799
  three.js's camera can only do the pinhole, so `EquidistantProjectionPass` renders the scene into an offscreen
788
800
  target with a deliberately wider field and resamples it in one fullscreen pass. Everything that *aims* at the scene
789
- rather than drawing it — the decor raycasts behind `isScreenPointOccluded` and `decorDistancesAt` goes through
790
- `directionFor`, since a point on the visible image no longer means what the pinhole camera thinks it means.
801
+ rather than drawing it — the decor raycasts behind `decorDistancesAt`, and the direction each phenomenon is stood
802
+ along — goes through `directionFor`, since a point on the visible image no longer means what the pinhole camera
803
+ thinks it means.
791
804
 
792
805
  This is also why a change of instrument **moves** shapes and not just resizes them (`SightingShapes.reproject`): a
793
806
  pixel only names a direction once a projection is named. Leaving positions alone is exactly how an object drawn in
@@ -797,9 +810,9 @@ Every case file here declares `eye`, because every one of them was watched rathe
797
810
  they were all rendered as photographs, which is what made Socorro's dynamite shack read as twice the size it
798
811
  subtends.
799
812
 
800
- One residual worth naming: an angular extent is stored as its *on-axis* value, and applied to a shape wherever it
801
- sits. For an object 9° off-axis subtending 9°, that is about 2.5% out. The overlay draws axis-aligned boxes and
802
- cannot express more; it is a fifth of the error it replaces, and it shrinks towards the centre of the frame.
813
+ One residual worth naming: an angular extent is stored as its *on-axis* value, and the plane that carries a
814
+ shape is sized by that same on-axis conversion wherever it stands. For an object 9° off-axis subtending 9°, that
815
+ is about 2.5% out; it is a fifth of the error it replaces, and it shrinks towards the centre of the frame.
803
816
 
804
817
  #### A pose long enough draws the sky
805
818
 
@@ -871,6 +884,27 @@ object's real width from one side, for the whole recording, and the narrowed wid
871
884
  every other instant. `SizeEstimate` (`src/engine/shape/SizeEstimate.ts`) accumulates that, reports a contradiction
872
885
  rather than clamping one, and the editor prints the result under the apparent size.
873
886
 
887
+ #### Where the shape is drawn
888
+
889
+ A plane facing the witness, scaled to the stated angle, looks the same from their eye at any distance — so the
890
+ scene has to put it *somewhere*, and where is a parameter of the picture, never a fact the recording states.
891
+ `PhenomenonDepth` (`src/engine/shape/PhenomenonDepth.ts`) is the one place that decides it, from five sources in
892
+ the order they outrank each other: a distance the recording **states** (none does yet — the tier exists for the
893
+ day a close encounter is written as a body in metres); a **hypothesis** the reader is trying; what the witness's
894
+ own walk **derives** (`ShapeDistance`, see below); what the crossings **bound**, the geometric middle of the
895
+ interval they leave, since any distance in it draws every crossing correctly; and, for the majority that
896
+ establishes nothing, a **conventional** few metres — in front of everything that was not declared to hide it,
897
+ which is what "not declared" has always meant here, and near rather than far so a shape drawn low in the frame
898
+ does not go under a distant ground. A shape drawn wholly inside another stands where that one stands, a hair
899
+ nearer: Socorro's insignia is painted on its craft, and a craft tried at five hundred metres must take its
900
+ insignia with it.
901
+
902
+ The hypothesis is the editor's **Show it at** slider — a metre to twenty kilometres, logarithmic — and it outranks
903
+ what the data establishes on purpose: a hypothesis is tested by watching it fail. Drag the craft out to five
904
+ hundred metres and it goes behind the patrol car it was drawn in front of; slide it back and it comes out. It is
905
+ never saved. The line under the slider says where the shape is drawn right now and on what basis, and the cross
906
+ withdraws the hypothesis.
907
+
874
908
  Most sightings constrain nothing at all — a light in an empty night sky crosses nothing — and the readout then says
875
909
  so. "Unknown" is the honest answer for a majority of cases, and saying it out loud is the entire point of not
876
910
  storing a number instead.
@@ -886,12 +920,15 @@ case's `sighting.json` from its `RR0Event`).
886
920
  - `src/engine/` — framework-agnostic core: `model/` (`Shape`, `Timeline`, `Sighting`), `record/` (`Recorder`,
887
921
  `SamplingClock`), `playback/` (`Player`), `persistence/` (JSON (de)serialization), `astronomy/` (vanilla solar
888
922
  position), `interop/` (real `@rr0/data` conversion, Node-only).
889
- - `src/render/CanvasRenderer.ts` — paints shapes onto a `<canvas>` 2D context.
890
- - `src/render3d/` the Three.js decor renderer (`SceneRenderer`) and its pure, dependency-free color logic
923
+ - `src/render/CanvasRenderer.ts` — paints shapes onto a `<canvas>` 2D context: the texture of each plane the
924
+ scene stands a shape on, the editing handles on the overlay, and the recording brush.
925
+ - `src/render3d/` — the Three.js decor renderer (`SceneRenderer`), the phenomena standing in it
926
+ (`PhenomenonSystem.ts`, drawn in their own decor-depth-tested pass), and its pure, dependency-free color logic
891
927
  (`skyColors.ts`), kept separate so the latter is unit-testable without a WebGL context.
892
- - `src/component/` — the four Web Components. `UfoElement` (`<rr0-ufo>`) owns the canvas/playback; `SceneElement`
893
- (`<rr0-scene>`) composes it directly (via `document.createElement`, not an inline template tag see the
894
- comment at that call site) rather than duplicating it, adding the 3D decor on top. `SightingEditorElement` and
928
+ - `src/component/` — the three Web Components and the playback layer under them. `UfoElement` (registered as
929
+ `rr0-ufo`, an inner layer and not a published component since 0.54.0) owns the timeline, the controls and the
930
+ pointer's canvas; `SceneElement` (`<rr0-scene>`) composes it directly (via `document.createElement`, not an inline
931
+ template tag — see the comment at that call site), standing the phenomena in its scene. `SightingEditorElement` and
895
932
  `SightingElement` (`<rr0-sighting>`) both compose a `SceneElement` in turn (not `UfoElement` directly) —
896
933
  the editor reaches through to its public `ufoElement` property for the actual canvas/timeline/appearance work
897
934
  (the toolbar edits the exact same `Sighting` instance the nested scene renders from, so an observer/time/
@@ -910,10 +947,9 @@ npm run dev # local demo (record + play), Vite dev server
910
947
  npm test # vitest
911
948
  npm run build # type-check + build the demo
912
949
  npm run build:embed # build dist-embed/rr0-sighting-editor.mjs
913
- npm run build:embed-ufo # build dist-embed-ufo/rr0-ufo.mjs
914
950
  npm run build:embed-scene # build dist-embed-scene/rr0-scene.mjs
915
951
  npm run build:embed-sighting # build dist-embed-sighting/rr0-sighting.mjs
916
- npm run build:all # all four
952
+ npm run build:all # all three
917
953
  npm run build:site # ufoathome.org, into dist-site/
918
954
  npm run build:comets # regenerate the comet catalog from JPL Horizons
919
955
  npm run build:satellites # regenerate the satellite catalog from CelesTrak's SATCAT