@motionstudies/core 0.1.0-alpha.22 → 0.1.0-alpha.24

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
@@ -265,3 +265,5 @@ The lab's **Fields** specimen renders a synthetic archipelago of seven instrumen
265
265
  `@motionstudies/core/timeline` supplies validated interval geometry, lookup, time mapping and service-time formatting. `@motionstudies/web/components/StudyTimeline` renders a controlled activity chart and scrubber; import `@motionstudies/web/study-timeline.css`. Pass `bins: {start, end, value}[]`, the time window, current `time` and `onSeek`. A `null` value means missing evidence; zero is a measured zero. `variant="bars"` (default) or `"line"` shares the same time axis. `maximum` optionally fixes the scale for comparisons. Playback and loading remain consumer-owned. See [the full contract](https://github.com/emmettl/motionstudies/blob/main/docs/STUDY-TIMELINE.md) and the Timeline lab specimen.
266
266
 
267
267
  Canvas editions can import `PausedVehicleFrame` from `@motionstudies/core/render-frame` without loading Three.js. The existing Three.js entry point re-exports the same class. Record only a frame actually submitted; change its visibility revision (or replace the tracker) when data, projection, size or selection changes. Time changes invalidate even while paused.
268
+
269
+ Plain playback bars can use `@motionstudies/web/components/TimelineScrubber` with `@motionstudies/web/timeline-scrubber.css`: the same visible rail and 44px interaction area as `StudyTimeline`, without activity bins. Pass the time window, time, seek callback and accessible time label.
@@ -0,0 +1,63 @@
1
+ /** Geographic continents from the airport reference; EU is not European Union membership. */
2
+ export declare const AIR_CONTINENTS: {
3
+ readonly AF: 'Africa';
4
+ readonly AN: 'Antarctica';
5
+ readonly AS: 'Asia';
6
+ readonly EU: 'Europe';
7
+ readonly NA: 'North America';
8
+ readonly OC: 'Oceania';
9
+ readonly SA: 'South America';
10
+ };
11
+ export type AirContinent = keyof typeof AIR_CONTINENTS;
12
+ export type AirContinentSource = 'ourairports' | 'override';
13
+ export type AirContinentStatus = 'known' | 'unknown-endpoint' | 'unknown-continent';
14
+ export interface AirContinentLabel {
15
+ readonly continent: AirContinent | null;
16
+ readonly status: AirContinentStatus;
17
+ }
18
+ export declare function isAirContinent(value: unknown): value is AirContinent;
19
+ /** Older releases and unobserved endpoints remain explicitly unknown. */
20
+ export declare function classifyAirEndpoint(endpoint?: {
21
+ readonly continent?: unknown;
22
+ } | null): AirContinentLabel;
23
+ /** Pass the canonical track index, never overlapping playback chunks. Counts are track segments, not flights. */
24
+ export declare function summarizeAirContinents(tracks: Iterable<{
25
+ readonly origin?: {
26
+ readonly continent?: unknown;
27
+ };
28
+ readonly destination?: {
29
+ readonly continent?: unknown;
30
+ };
31
+ }>): {
32
+ trackSegments: number;
33
+ origin: {
34
+ known: number;
35
+ unknownEndpoint: number;
36
+ unknownContinent: number;
37
+ continents: {
38
+ AF: number;
39
+ AN: number;
40
+ AS: number;
41
+ EU: number;
42
+ NA: number;
43
+ OC: number;
44
+ SA: number;
45
+ };
46
+ knownFraction: number | null;
47
+ };
48
+ destination: {
49
+ known: number;
50
+ unknownEndpoint: number;
51
+ unknownContinent: number;
52
+ continents: {
53
+ AF: number;
54
+ AN: number;
55
+ AS: number;
56
+ EU: number;
57
+ NA: number;
58
+ OC: number;
59
+ SA: number;
60
+ };
61
+ knownFraction: number | null;
62
+ };
63
+ };
@@ -0,0 +1,43 @@
1
+ /** Geographic continents from the airport reference; EU is not European Union membership. */
2
+ export const AIR_CONTINENTS = {
3
+ AF: 'Africa', AN: 'Antarctica', AS: 'Asia', EU: 'Europe',
4
+ NA: 'North America', OC: 'Oceania', SA: 'South America',
5
+ };
6
+ export function isAirContinent(value) {
7
+ return typeof value === 'string' && Object.hasOwn(AIR_CONTINENTS, value);
8
+ }
9
+ /** Older releases and unobserved endpoints remain explicitly unknown. */
10
+ export function classifyAirEndpoint(endpoint) {
11
+ if (!endpoint)
12
+ return { continent: null, status: 'unknown-endpoint' };
13
+ if (!isAirContinent(endpoint.continent))
14
+ return { continent: null, status: 'unknown-continent' };
15
+ return { continent: endpoint.continent, status: 'known' };
16
+ }
17
+ /** Pass the canonical track index, never overlapping playback chunks. Counts are track segments, not flights. */
18
+ export function summarizeAirContinents(tracks) {
19
+ const empty = () => ({
20
+ known: 0, unknownEndpoint: 0, unknownContinent: 0,
21
+ continents: { AF: 0, AN: 0, AS: 0, EU: 0, NA: 0, OC: 0, SA: 0 },
22
+ });
23
+ const counts = { origin: empty(), destination: empty() };
24
+ let trackSegments = 0;
25
+ for (const track of tracks) {
26
+ trackSegments++;
27
+ for (const side of ['origin', 'destination']) {
28
+ const label = classifyAirEndpoint(track[side]), count = counts[side];
29
+ if (label.continent) {
30
+ count.known++;
31
+ count.continents[label.continent]++;
32
+ }
33
+ else if (label.status === 'unknown-endpoint')
34
+ count.unknownEndpoint++;
35
+ else
36
+ count.unknownContinent++;
37
+ }
38
+ }
39
+ const coverage = (side) => ({
40
+ ...counts[side], knownFraction: trackSegments ? counts[side].known / trackSegments : null,
41
+ });
42
+ return { trackSegments, origin: coverage('origin'), destination: coverage('destination') };
43
+ }
package/domain/air.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { AirContinent, AirContinentSource } from '../air-continents.ts';
1
2
  export type AirSample = readonly [
2
3
  time: number,
3
4
  longitude: number,
@@ -13,6 +14,9 @@ export interface AirEndpoint {
13
14
  /** Study-local seconds at the observed approach/departure boundary; not a scheduled time. */
14
15
  readonly time: number;
15
16
  readonly evidence: 'observed-endpoint';
17
+ /** Airport geography, separate from the inferred route evidence. Absent in older releases. */
18
+ readonly continent?: AirContinent;
19
+ readonly continentSource?: AirContinentSource;
16
20
  }
17
21
  export interface AirTrack {
18
22
  readonly id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motionstudies/core",
3
- "version": "0.1.0-alpha.22",
3
+ "version": "0.1.0-alpha.24",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Transport contracts and motion primitives for Motion Studies.",
@@ -20,6 +20,11 @@
20
20
  "tag": "next"
21
21
  },
22
22
  "exports": {
23
+ "./air-continents": {
24
+ "types": "./air-continents.d.ts",
25
+ "import": "./air-continents.js",
26
+ "default": "./air-continents.js"
27
+ },
23
28
  "./air-search": {
24
29
  "types": "./air-search.d.ts",
25
30
  "import": "./air-search.js",