@rr0/ufoathome 0.2.0 → 0.3.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 CHANGED
@@ -8,9 +8,9 @@ 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 three vanilla
11
+ dependency-light engine (keyframe timeline, recording, playback, Canvas2D rendering) wrapped in four 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. One of the three (`<rr0-scene>`) does pull in [Three.js](https://threejs.org/)
13
+ required by the consuming page. One of the four (`<rr0-scene>`) does pull in [Three.js](https://threejs.org/)
14
14
  for an optional 3D backdrop — see [`<rr0-scene>`](#rr0-scene--3d-decor) below for why that's an isolated,
15
15
  opt-in bundle rather than a project-wide dependency.
16
16
 
@@ -22,7 +22,9 @@ its default behavior and `<rr0-ufo-recorder>` is the one that needs a qualifier
22
22
  real-world time and place, with no UFO-specific logic of its own — today it composes a nested `<rr0-ufo>` for the
23
23
  common case (see its section below), but the decor itself could back other kinds of reconstructions later. A fully
24
24
  generic version (accepting arbitrary overlay content instead of always creating its own `<rr0-ufo>`) is a natural
25
- follow-up, not implemented yet.
25
+ follow-up, not implemented yet. `<rr0-ufo-witnesses>` follows the same "adds a qualifier" logic as the recorder:
26
+ it *adds* a multi-witness selector on top of `<rr0-ufo>`, for cases that have more than one witness's own
27
+ recording of the same sighting.
26
28
 
27
29
  See the [Wiki](https://github.com/RR0/UfoAtHome/wiki) for the project's history, and a live example embedded in
28
30
  [rr0.org's UFO@home page](https://rr0.org/science/crypto/ufo/enquete/projet/UfoAtHome.html) and in its
@@ -34,26 +36,27 @@ See the [Wiki](https://github.com/RR0/UfoAtHome/wiki) for the project's history,
34
36
  npm install @rr0/ufoathome
35
37
  ```
36
38
 
37
- Three self-contained, pre-built ES modules are published — each self-registers its custom element as soon as it's
39
+ Four self-contained, pre-built ES modules are published — each self-registers its custom element as soon as it's
38
40
  imported, no explicit setup call needed:
39
41
 
40
42
  ```html
41
43
  <script type="module" src="/node_modules/@rr0/ufoathome/dist-embed-ufo/rr0-ufo.mjs"></script>
42
44
  <script type="module" src="/node_modules/@rr0/ufoathome/dist-embed/rr0-ufo-recorder.mjs"></script>
43
45
  <script type="module" src="/node_modules/@rr0/ufoathome/dist-embed-scene/rr0-scene.mjs"></script>
46
+ <script type="module" src="/node_modules/@rr0/ufoathome/dist-embed-witnesses/rr0-ufo-witnesses.mjs"></script>
44
47
  ```
45
48
 
46
49
  or, from a bundler:
47
50
 
48
51
  ```ts
49
- import "@rr0/ufoathome/ufo" // registers <rr0-ufo>
50
- import "@rr0/ufoathome/recorder" // registers <rr0-ufo-recorder> (and <rr0-ufo>, which it composes)
51
- import "@rr0/ufoathome/scene" // registers <rr0-scene> (and <rr0-ufo>, which it composes)
52
+ import "@rr0/ufoathome/ufo" // registers <rr0-ufo>
53
+ import "@rr0/ufoathome/recorder" // registers <rr0-ufo-recorder> (and <rr0-ufo>, which it composes)
54
+ import "@rr0/ufoathome/scene" // registers <rr0-scene> (and <rr0-ufo>, which it composes)
55
+ import "@rr0/ufoathome/witnesses" // registers <rr0-ufo-witnesses> (and <rr0-ufo>, which it composes)
52
56
  ```
53
57
 
54
58
  Only load the one(s) a given page actually needs — `rr0-scene.mjs` in particular pulls in Three.js and is far
55
- heavier than the other two (see its section below), so pages that just need playback should stick to
56
- `rr0-ufo.mjs`.
59
+ heavier than the others (see its section below), so pages that just need playback should stick to `rr0-ufo.mjs`.
57
60
 
58
61
  ## `<rr0-ufo>` — read-only playback
59
62
 
@@ -73,6 +76,8 @@ The lightweight component (~9KB): a canvas plus Play/Pause/Loop/seek controls. U
73
76
  | `renderer` | property (readonly) | The `CanvasRenderer` instance painting onto that canvas |
74
77
  | `refresh()` | method | Re-reads the timeline's duration into the seek slider and repaints the current frame — call after externally mutating `sighting.timeline` |
75
78
  | `loadFromSrc(url)` | method (async) | What the `src` attribute triggers internally; can be called directly too |
79
+ | `enableClickToPlay` | property (get/set, default `true`) | Whether clicking the canvas toggles Play/Pause (see below). Composing elements that need the canvas's own click for something else set this to `false` — see `<rr0-ufo-recorder>`. |
80
+ | `fullscreenTarget` | property (get/set, default: the component's own stage) | The element the fullscreen button requests fullscreen on. Composing elements that need a *different* element fullscreened set this — see `<rr0-scene>`. |
76
81
 
77
82
  Playback matches the observation's *real reported duration* when it's known: set `time`/`endTime`, or `time`/
78
83
  `durationSeconds`, in the [data format](#data-format) (`durationSeconds` takes precedence over `endTime` if both are
@@ -82,18 +87,38 @@ bar show real clock times when `time` has an hour (e.g. `02:45` → `02:50`); ot
82
87
  duration actually available (the declared one if known, else the recording's own length). Playback loops by
83
88
  default — click the loop button (pressed = looping) to play once and stop instead.
84
89
 
90
+ Clicking anywhere on the canvas also toggles Play/Pause (not just the button), matching common video-player UX.
91
+ While playing, the toolbar and the fullscreen button (top-right, semi-transparent over the content) auto-hide and
92
+ only reappear on hover — always shown while paused/stopped. The fullscreen button uses the standard Fullscreen API
93
+ (`requestFullscreen`/`exitFullscreen`); exiting with Escape is native browser behavior, nothing custom.
94
+
95
+ Labels (Play/Pause, Auto-replay, Current position, Duration, Fullscreen) are translated (English/French) based on
96
+ the visitor's `navigator.languages`, falling back to English — there's no language-picker UI, this is the only
97
+ mechanism.
98
+
85
99
  ## `<rr0-ufo-recorder>` — full editor
86
100
 
87
- The authoring component (~17KB): everything `<rr0-ufo>` has, plus a shape/appearance toolbar (oval/saucer/
88
- triangle presets, color, transparency, halo) and drag-to-record. It composes a `<rr0-ufo>` internally rather
89
- than duplicating the canvas/playback codesee [Architecture](#architecture).
101
+ The authoring component (~540KB gzip — see below for why): everything `<rr0-ufo>` has, plus a shape/appearance
102
+ toolbar (oval/saucer/triangle presets, color, transparency, halo) and drag-to-record. It composes a nested
103
+ `<rr0-scene>` internally not a bare `<rr0-ufo>` so the shape being drawn is always seen against the
104
+ sighting's own real sky, computed live from whatever latitude/longitude/heading/orientation/observation-time
105
+ fields the toolbar currently holds (see [Architecture](#architecture)). This absorbs `<rr0-scene>`'s own
106
+ Three.js/`astronomy-engine` weight on top of the authoring-only code this element already carried (Recorder
107
+ engine, SamplingClock, appearance toolbar) — a page that only needs to *play* a sighting (the common case: an
108
+ rr0.org case dossier) should still embed the much lighter `<rr0-ufo>` (or `<rr0-scene>` alone) directly, never
109
+ this heavier authoring component.
90
110
 
91
111
  ```html
92
112
  <rr0-ufo-recorder></rr0-ufo-recorder>
93
113
  ```
94
114
 
95
115
  Usage: click **Record**, move the pointer over the canvas to draw the UFO's path, click **Stop**, then **Play** to
96
- replay it.
116
+ replay it. The nested `<rr0-ufo>`'s `enableClickToPlay` is set to `false` here — a completed recording drag also
117
+ fires a native "click" on the canvas, which would otherwise spuriously toggle playback right after recording.
118
+
119
+ All of the toolbar's own labels (shape presets, Color/Transparency/Halo, Add shape, Record/Stop, Export JSON,
120
+ Duration) are translated (English/French) the same way `<rr0-ufo>`'s own labels are — based on
121
+ `navigator.languages`, no picker UI.
97
122
 
98
123
  | Member | Kind | Description |
99
124
  |---|---|---|
@@ -102,26 +127,104 @@ replay it.
102
127
 
103
128
  ## `<rr0-scene>` — 3D decor
104
129
 
105
- The environmental variant (~180KB gzip, dominated by [Three.js](https://threejs.org/) — this is by far the heaviest
106
- of the three bundles, load it only on pages that want it): everything `<rr0-ufo>` has, composited over a 3D
130
+ The environmental variant (~530KB gzip [Three.js](https://threejs.org/) plus
131
+ [`astronomy-engine`](https://github.com/cosinekitty/astronomy)'s planetary/lunar position tables, which don't
132
+ tree-shake since they're one shared data table used internally for every body — this is by far the heaviest
133
+ of the four bundles, load it only on pages that want it): everything `<rr0-ufo>` has, composited over a 3D
107
134
  sky/horizon/starfield backdrop instead of a plain background. Same markup and members as `<rr0-ufo>` (`src`,
108
- `sightingData`, `loadFromSrc`) — it's a drop-in upgrade.
135
+ `sightingData`, `loadFromSrc`, `enableClickToPlay`) — it's a drop-in upgrade, including click-to-play/pause
136
+ anywhere on the scene (the nested `<rr0-ufo>`'s transparent canvas covers the whole stage). The fullscreen button
137
+ fullscreens the *whole* scene (3D backdrop included), not just the nested `<rr0-ufo>`'s own overlay — it sets the
138
+ nested element's `fullscreenTarget` to its own outer stage for this.
109
139
 
110
140
  ```html
111
141
  <rr0-scene src="sighting.json"></rr0-scene>
112
142
  ```
113
143
 
114
- Lighting (sky darkness/color, star visibility) is computed from the sighting's own recorded `time`/`place` via
115
- `src/engine/astronomy/SunPosition.ts`a vanilla (no dependency) implementation of the standard NOAA/Spencer
116
- low-precision solar position approximation. Deliberately scoped down for this first pass: only the sun's *altitude*
117
- drives the sky, not azimuth — positioning a sun/moon disc (or anything else) at a specific compass direction needs
118
- the witness's viewing heading, which isn't part of the data model yet. Precipitation and optical effects (lens
119
- flare, halos, mirage) are future work; see `src/render3d/SceneRenderer.ts`.
144
+ **Real astronomy for misidentification spotting.** A recurring cause of UFO reports is a mundane astronomical
145
+ object or atmospheric optical effect Venus (by far the most commonly misreported "UFO"), other planets, the
146
+ Moon, lens flare, or halo phenomena like sun dogs/moon dogs. `<rr0-scene>` renders the sky astronomically: real
147
+ Sun/Moon/Venus/Mars/Jupiter/Saturn positions and the Moon's phase via
148
+ [`astronomy-engine`](https://github.com/cosinekitty/astronomy) (see `src/engine/astronomy/CelestialPositions.ts`),
149
+ and a real star catalog (see below) instead of a randomized field, filtered to naked-eye visibility
150
+ (magnitude ≤ 7.5) since these are human eyewitness observations, not instrument-assisted ones. The sky's
151
+ darkness/color follows the sun's altitude (day/twilight bands/night), and its dawn/dusk glow is anchored on the
152
+ sun's real compass direction, not spread uniformly around the horizon — see `src/render3d/skyColors.ts`.
153
+
154
+ The witness's own pose — geographic position, elevation, and viewing heading/pitch/field of view — can vary over
155
+ the sighting's timeline via `observerTrack` in the sighting JSON (a keyframe array alongside `timeline`, same
156
+ hold-last/interpolated-lookup shape — see `src/engine/model/ObserverTrack.ts`), driving both the camera's own
157
+ orientation and which real-world instant the astronomy is computed for as playback advances. Older recordings
158
+ with no `observerTrack` fall back to the legacy static `place[0]` (see `resolveObserverPoseAt` in
159
+ `src/engine/model/Sighting.ts`) — usable for sky darkness/color and camera pitch/fov, but with no compass heading
160
+ to orient the camera by.
161
+
162
+ `src/engine/astronomy/SunPosition.ts` (the original vanilla, dependency-free NOAA/Spencer solar position
163
+ approximation) stays in the repo, tested, and still backs `skyBrightness()`'s twilight-band classification — but
164
+ the live rendering path now uses `astronomy-engine` for the Sun too, for a single source of truth and to get the
165
+ Sun's azimuth from the same call used for the sky's directional glow.
166
+
167
+ `<rr0-ufo-recorder>` has editor fields for the witness's latitude/longitude/heading and the observation's start
168
+ date/time (all optional) — filling in lat+lng writes both the legacy `place` and a single t=0 `observerTrack`
169
+ keyframe (elevation/pitch/field of view stay at neutral defaults; there's no UI yet for authoring the observer
170
+ *moving* over time, only a single static pose per recording).
171
+
172
+ Not yet done: sun dog/moon dog/halo rendering, a real (non-flat) ground/terrain, precipitation and other optical
173
+ effects (lens flare, mirage), and a multi-keyframe `observerTrack` authoring UI (today the recorder can only set
174
+ one static pose; an observer that moves/re-orients mid-recording still needs hand-authored or scripted JSON). The
175
+ Moon's phase currently only dims/brightens its disc's overall
176
+ color rather than rendering a geometrically accurate crescent shape — a natural follow-up.
177
+
178
+ **Regenerating the star catalog.** `src/assets/stars-mag7.5.bin` (a compact binary asset, four concatenated
179
+ `Float32Array` sections: ra/dec/mag/ci — see `src/render3d/StarCatalog.ts` for the exact layout) is generated
180
+ from the [HYG Database v4.1](https://github.com/astronexus/HYG-Database) (CC BY-SA), filtered to magnitude ≤ 7.5.
181
+ To regenerate it: download `hyg/CURRENT/hygdata_v41.csv` from that repo into `scripts/data/hygdata_v41.csv`
182
+ (gitignored — not checked in, ~34MB), then run `npm run build:stars`. The generated `.bin`/`.json` pair *is*
183
+ checked in (~400KB) since it's small and doesn't need regenerating on every install.
120
184
 
121
185
  The UFO shape itself deliberately stays a 2D overlay on top of the 3D decor, never "upgraded" to a 3D object: it's
122
186
  what the witness reported — possibly a misidentification or optical effect — not something to interpret as a real
123
187
  3D shape. Only the surrounding environment, independently computable from real astronomy, is rendered in 3D.
124
188
 
189
+ ## `<rr0-ufo-witnesses>` — multi-witness selector
190
+
191
+ A case can have more than one witness, each with their own recording (their own `sighting.json`) of the same
192
+ event. This component lets a page switch between them instead of only ever embedding one fixed `<rr0-ufo>` — it
193
+ composes a nested `<rr0-ufo>` the same way `<rr0-scene>` does, and is otherwise just as lightweight.
194
+
195
+ ```html
196
+ <rr0-ufo-witnesses src="witnesses.json"></rr0-ufo-witnesses>
197
+ ```
198
+
199
+ `src` points not at a `sighting.json` directly, but at a small manifest — a plain JSON array of each witness's own
200
+ `SightingRecordingJson` URL (typically relative to the case's own page, same as `<rr0-ufo>`'s own `src`):
201
+
202
+ ```json
203
+ ["chiles-sighting.json", "whitted-sighting.json"]
204
+ ```
205
+
206
+ No labels or ids are duplicated in the manifest itself — each witness's display name and the shared case id
207
+ grouping them together are read from that witness's *own* file (`witnessName`/`caseId`, see
208
+ [Data format](#data-format)), so there's a single source of truth and nothing to drift out of sync. This means
209
+ every listed witness's recording is fetched upfront (to read its name), not lazily on selection — fine at the
210
+ scale a case's witness list actually has. If a witness has no `witnessName`, its `witnessId` is shown instead, or
211
+ the URL itself as a last resort. A mismatched `caseId` across the listed witnesses logs a console warning (doesn't
212
+ block) — likely means unrelated recordings got listed together by mistake.
213
+
214
+ | Member | Kind | Description |
215
+ |---|---|---|
216
+ | `src` | attribute | URL of a witness manifest (above), fetched automatically on connect and whenever the attribute changes |
217
+ | `witnessUrls` | property (get/set) | The manifest as a plain array of URLs, for programmatic use instead of `src` |
218
+ | `loadFromSrc(url)` | method (async) | What the `src` attribute triggers internally; can be called directly too |
219
+
220
+ The selector (a plain `<select>` of witness labels) is hidden entirely when there are 0 or 1 witnesses — no point
221
+ showing a dropdown with nothing to actually choose between. The first witness loads automatically once the list is
222
+ known; switching the selector loads that witness's already-fetched recording into the nested `<rr0-ufo>` (no
223
+ re-fetch). Setting `witnessUrls` again (e.g. a manifest refresh) keeps the current selection if that witness is
224
+ still present, instead of resetting back to the first.
225
+
226
+ The "Witness" label itself is translated (English/French) the same way as `<rr0-ufo>`'s own labels.
227
+
125
228
  ## Data format
126
229
 
127
230
  Both components read/write a plain, JSON-serializable `SightingRecordingJson`:
@@ -133,7 +236,9 @@ interface SightingRecordingJson {
133
236
  endTime?: { year?: number, month?: number, day?: number, hour?: number, minute?: number, second?: number } // alternative to durationSeconds
134
237
  durationSeconds?: number // alternative to endTime; takes precedence if both are set
135
238
  place?: { lat: number, lng: number }[]
136
- witnessId?: string
239
+ witnessId?: string // opaque internal reference — no PII beyond a display name (see witnessName)
240
+ witnessName?: string // for cases where the witness is already publicly named in the published material (e.g. Chiles-Whitted) — omit for anonymous witnesses
241
+ caseId?: string // shared by every witness's own sighting.json for the same case — see <rr0-ufo-witnesses>
137
242
  timeline: {
138
243
  keyframes: Array<{
139
244
  t: number // milliseconds since recording start
@@ -169,11 +274,15 @@ case's `sighting.json` from its `RR0Event`).
169
274
  - `src/render/CanvasRenderer.ts` — paints shapes onto a `<canvas>` 2D context.
170
275
  - `src/render3d/` — the Three.js decor renderer (`SceneRenderer`) and its pure, dependency-free color logic
171
276
  (`skyColors.ts`), kept separate so the latter is unit-testable without a WebGL context.
172
- - `src/component/` — the three Web Components. `UfoElement` (`<rr0-ufo>`) owns the canvas/playback; `UfoRecorderElement`
173
- and `SceneElement` (`<rr0-scene>`) both compose it (via `document.createElement`, not an inline template tag — see
174
- the comment at that call site) rather than duplicating it, adding recording/appearance-editing or the 3D decor on
175
- top, respectively.
176
- - Playback is deliberately discrete (hold-last-keyframe, no interpolation), matching the original applet's behavior.
277
+ - `src/component/` — the four Web Components. `UfoElement` (`<rr0-ufo>`) owns the canvas/playback; `SceneElement`
278
+ (`<rr0-scene>`) and `WitnessSelectorElement` (`<rr0-ufo-witnesses>`) compose it directly (via
279
+ `document.createElement`, not an inline template tag — see the comment at that call site) rather than duplicating
280
+ it, adding the 3D decor or the multi-witness selector on top, respectively. `UfoRecorderElement` composes a
281
+ `SceneElement` (not `UfoElement` directly) and reaches through to its public `ufoElement` property for the
282
+ actual canvas/timeline/appearance work — the toolbar edits the exact same `Sighting` instance the nested scene
283
+ renders from, so an observer/time/appearance change needs no separate sync step to reach the sky.
284
+ - Playback linearly interpolates shapes between a source's surrounding keyframes for smooth motion
285
+ (`Timeline.getInterpolatedShapeAt`/`Shape.lerpShape`), holding at the ends of its recorded range.
177
286
  - Recording samples the pointer position at a configurable rate via `requestAnimationFrame`, not on every
178
287
  `pointermove` event.
179
288
 
@@ -0,0 +1 @@
1
+ const e={play:"Lecture",pause:"Pause",autoReplay:"Lecture automatique",currentPosition:"Position actuelle",duration:"Durée",fullscreen:"Plein écran",exitFullscreen:"Quitter le plein écran"};export{e as ufoMessages_fr};
@@ -0,0 +1 @@
1
+ const e={oval:"Ovale",saucer:"Soucoupe",triangle:"Triangle",color:"Couleur",transparency:"Transparence",halo:"Halo",shape:"Forme",addShape:"Ajouter une forme",samplingRate:"Fréquence d'échantillonnage (ms)",duration:"Durée (s)",durationPlaceholder:"durée de l'enregistrement",export:"Exporter le JSON",record:"Enregistrer",stop:"Arrêter",latitude:"Latitude",longitude:"Longitude",heading:"Orientation (°)",headingPlaceholder:"inconnu",pitch:"Inclinaison (°)",observationTime:"Début de l'observation (optionnel)",yearPlaceholder:"année",monthPlaceholder:"mois",dayPlaceholder:"jour",hourPlaceholder:"heure",minutePlaceholder:"min"};export{e as ufoRecorderMessages_fr};