Files
gods-eye-view/src/layers/satellites/policy.js
T
Sameh Khamis 0c4da3cdb2 Keep one world clock that every timed layer reads
Core told time eight ways: Date.now() in most layers, JulianDate.now() for
flights and military, Cesium's clock frozen at page load for the sky, the
weather clock for observed imagery. They now read one clock,
src/time/worldClock.js, exported as ./time.

Live is a mode, not a timestamp: while live the clock is the wall clock,
so every layer draws as before. Away from live only the layers that follow
the clock move with it. The weather products follow it, as they followed
the weather transport; a host opts others in with follow(id).

- The weather clock takes its time from the world clock and drives its
  replay, stepping its frames as before.
- Satellites, launches, flights and military (still 30 s and 15 s behind),
  transit, GNSS, Recent Imagery, street level, the HUD and the celestial
  ring read worldTimeFor(id). Cadences stay on the wall clock.
- Cesium's sky (stars, sun, moon, atmosphere, lighting) draws at the world
  time each frame instead of the moment the page loaded.
- Weather frames and satellites declare coverage() for a host's timeline.
2026-11-01 21:47:32 -08:00

185 lines
6.2 KiB
JavaScript

import * as Cesium from 'cesium';
import { satelliteClassColor } from '../../data/satelliteClass.js';
/**
* Satellite Orbits — Real-time positions via CelesTrak TLE + SGP4 propagation.
*
* Loads six CelesTrak groups (~840 sats): stations, visual, GPS, GLONASS,
* Galileo, and the geosynchronous belt. Optional dense mode (setParams
* catalog:'dense') adds the Starlink shell as points-only extras.
* Renders positions via PointPrimitiveCollection, orbital paths as polylines.
* Click any satellite to track it with camera follow + orbital path.
*
* ISS gets special treatment: larger point, persistent host label, path shown by default.
*/
export const ISS_NORAD = 25544;
export const ISS_OVERLAY_SOURCE_ID = 'satellites-iss';
export const ISS_OVERLAY_SOURCE_OPTIONS = Object.freeze({
cohortLimit: 1,
collisionCapacity: 1,
moving: true,
solveIntervalMs: 125,
});
export const ORBIT_PATH_STEPS = 180;
// points per orbital path
export const POSITION_UPDATE_MS = 1000;
/** How far either side of now SGP4 on current element sets stays meaningful. */
export const SATELLITE_COVERAGE_MS = 14 * 24 * 3_600_000;
// re-propagate every 1s (SGP4 is smooth at this rate)
export const RING_ROTATION_MS = 1000;
// re-align baked orbit rings to current GMST every 1s
/**
* CelesTrak groups loaded as the core catalog, in dedupe-priority order:
* a satellite that appears in multiple groups keeps the FIRST (most specific)
* tag. `path` is the upstream GROUP name forwarded by the /api/celestrak
* proxy; `tag` is the internal group key used for POINT_STYLES lookup.
* Note: CelesTrak's GLONASS group is named 'glo-ops' (not
* 'glonass-operational' — that name 404s upstream).
*/
export const CATALOG_GROUPS = [
{ tag: 'stations', path: 'stations' },
{ tag: 'visual', path: 'visual' },
{ tag: 'gps-ops', path: 'gps-ops' },
{ tag: 'glonass', path: 'glo-ops' },
{ tag: 'galileo', path: 'galileo' },
{ tag: 'geo', path: 'geo' },
];
// Dense-catalog mode (setParams({ catalog: 'dense' })): Starlink shell as
// points-only extras — no labels, no detection-overlay participation, and a
// relaxed propagation budget (round-robin, ~1/5 of core cadence per sat).
export const DENSE_GROUP_PATH = 'starlink';
export const DENSE_REFRESH_FRAMES = 300;
// full dense pass spread over ~300 frames (~5s @ 60fps)
export const DENSE_CREATE_CHUNK = 1500;
// satrec builds per macro-task while loading
/**
* Tracked-entity camera offset, east-north-up meters (Entity.viewFrom).
* Magnitude ≈ 726 km — the user-validated "slightly zoomed out" framing for
* LEO: far enough that per-frame satellite motion doesn't stutter the camera
* or smear the label, with +Z biasing the view down onto the satellite.
* High orbits (MEO nav / GEO belt) scale this up so the ring stays in frame.
*/
export const TRACK_VIEW_FROM_LEO = new Cesium.Cartesian3(
-450000,
-450000,
350000,
);
export const HIGH_ORBIT_ALTITUDE_M = 2000000;
export const TRACK_VIEW_FROM_HIGH_SCALE = 4;
// ≈ 2900 km back for MEO/GEO
/**
* Shared per-group point styling — single source of truth used by BOTH the
* creation site (update) and tracking restore (_clearTracking) so deselecting
* a satellite never loses the original palette (WS-D3).
*
* Colors come from `satelliteClass.js` so the dot, the class label on the
* card, and the legend swatch on the layer row can never disagree. Only
* pixelSize/outline live here — those encode per-group prominence, not class.
* Converted once at module load; per-point color is a plain primitive
* attribute, so classification costs nothing per frame.
*/
const POINT_OUTLINE = Cesium.Color.WHITE.withAlpha(0.3);
const _classColor = (group) =>
Cesium.Color.fromCssColorString(satelliteClassColor(group));
export const POINT_STYLES = {
// The ISS keeps its own long-standing red hero styling rather than the
// STATION class color: it is the object most users open this layer for, it
// carries a permanent name label, and its size/outline already set it apart.
// Its card still reads "STATION · ISS", so the class stays legible.
iss: {
pixelSize: 12,
color: Cesium.Color.fromCssColorString('#ff4444'),
outlineColor: POINT_OUTLINE,
outlineWidth: 2,
},
stations: {
pixelSize: 8,
color: _classColor('stations'),
outlineColor: POINT_OUTLINE,
outlineWidth: 0,
},
visual: {
pixelSize: 6,
color: _classColor('visual'),
outlineColor: POINT_OUTLINE,
outlineWidth: 0,
},
// Nav constellations (GPS / GLONASS / Galileo) resolve to one shared NAV
// color; 6px so the MEO shells read as clearly as the old visual group did.
'gps-ops': {
pixelSize: 6,
color: _classColor('gps-ops'),
outlineColor: POINT_OUTLINE,
outlineWidth: 0,
},
glonass: {
pixelSize: 6,
color: _classColor('glonass'),
outlineColor: POINT_OUTLINE,
outlineWidth: 0,
},
galileo: {
pixelSize: 6,
color: _classColor('galileo'),
outlineColor: POINT_OUTLINE,
outlineWidth: 0,
},
geo: {
pixelSize: 5,
color: _classColor('geo'),
outlineColor: POINT_OUTLINE,
outlineWidth: 0,
},
// Dense-mode extras (Starlink): dim, small, points-only.
dense: {
pixelSize: 3,
color: _classColor('dense').withAlpha(0.9),
outlineColor: POINT_OUTLINE,
outlineWidth: 0,
},
};
/**
* Physically DOCKED vehicles are separate real tracks sharing one position: the
* station and everything berthed to it sit within metres of each other. Their
* ambient labels therefore stack underneath the tracked card, which is what the
* owner saw. This radius is deliberately tight — it must catch a docked stack
* and nothing else, so an unrelated satellite in a similar orbit is never
* suppressed. Formation-flying pairs are km apart; a docked stack is ~100 m.
*/
export const DOCKED_COMPANION_RADIUS_M = 2000;
/** The scan is O(points); the tracked label is rebuilt every frame, so throttle. */
export const DOCKED_SCAN_INTERVAL_MS = 1000;
/**
* Shared-context refresh interval. The tracked satellite is re-propagated
* every rendered frame; the voice context only needs to be current to about
* the propagation cadence, so this refreshes on the same 1 s beat instead of
* allocating a record 60 times a second.
*/
export const CONTEXT_REFRESH_INTERVAL_MS = 1000;