Physics

This document explains the physical formulas used by the visualisations and which aspects of each view are physically accurate vs. stylised.

Quick summary

View Orbital speeds Sizes Distances Notes
solar-system Accurate Stylised Stylised Compressed for visibility
scale Accurate Real Real Planets are tiny — use markers
galaxy Accurate Stylised Stylised Flat rotation curve via shader
universe Stylised Stylised Named objects at real RA/Dec

All per-planet and per-moon astronomical data lives in src/data/planets.js and src/data/moons.js. Physical constants are in src/data/constants.js. The formulas below are implemented in src/physics/formulas.js.

Orbital periods (Kepler)

We use the sidereal orbital period of each planet directly (NASA factsheets). At 1× simulation speed, Earth completes one full orbit in EARTH_SIM_PERIOD_SEC = 20 seconds of real time. Every other planet orbits at the same ratio relative to Earth as it does in reality — Mercury zips around every 4.8 s, Neptune crawls for 3 300 s.

ω = 2π / (EARTH_SIM_PERIOD_SEC × (period_days / 365.25))

This is equivalent to Kepler’s 3rd law (T² ∝ a³) if you already know the orbital period — we’re not computing periods from gravitation, we’re just re-using the empirical periods.

Elliptical orbits

Unused in the shipped views but kept as a reference formula: for a body on an ellipse with semi-major axis a and eccentricity e, the Sun-to-body distance as a function of true anomaly θ is

r(θ) = a(1 - e²) / (1 + e·cos θ)

Conservation of angular momentum (Kepler’s 2nd law) gives dθ/dt ∝ 1/r², so the body speeds up near perihelion and slows down at aphelion. This is implemented in keplerRadius() in src/physics/formulas.js.

Galactic rotation

The naive Keplerian expectation for the Milky Way (ω ∝ 1/r^1.5) is wrong — stars far from the galactic centre orbit much faster than their visible mass would predict. This discrepancy is the primary evidence for dark matter: the Milky Way’s rotation curve is flat out to large radii.

We approximate this with a constant circular velocity:

v_c  ≈ 220 km/s  (essentially constant across the disk)
ω(r) = v_c / r

Inner stars have higher angular velocity; outer stars lag. Stars at the Sun’s radius (~26 000 ly) orbit at the Sun’s own rate, so in the galaxy view they appear nearly stationary while inner stars whip around the bulge. This is implemented in the vertex shader in galaxy.html (see the starUniforms and vertex shader blocks near the top of the script).

Special relativity (reference)

For completeness, the shipped views don’t require relativistic physics, but src/physics/formulas.js exposes lorentzFactor(β) and relativisticTravel() in case a future view needs them:

γ = 1 / √(1 - β²)                 (β = v/c)
t_ship = t_earth / γ              (time dilation)

At β = 0.99, γ ≈ 7.09 — a 10-year Earth-frame trip is 1.4 years for the crew.

Scale and compression

See scales.md for the specific compression factors used on each view and why “true proportional” visualisation doesn’t fit on a screen.

Lunar phase model

The phase angle (used for illumination, phase name, and the next-phase scanner) is computed from the same Meeus-abridged Brown’s-series longitude that drives the Moon’s ecliptic position (see Lunar geocentric position below):

Accuracy: a few minutes on the time of any new/full/quarter phase across decades, vs ±0.5 day for the linear single-term model that this replaced. nextLunarPhase in src/physics/events.js runs Newton iteration on φ (initial step from the synodic-rate estimate, ±3-day clamp on subsequent corrections), converging to ~1e-9 rad in 3–4 iterations. Functions in src/physics/moon.js.

Lunar geocentric position (Meeus abridged)

For eclipse detection and a Moon that visibly crosses the ecliptic at the right inclination, the simple linear model is not enough — the Moon’s periodic perturbations push its real ecliptic latitude away from zero by up to ±5°, and that latitude is exactly what determines whether a new/full moon also produces an eclipse. We use the leading 6 longitude + 6 latitude

Terms scaled by Earth’s orbital eccentricity e(T) = 1 − 0.002516·T − 0.0000074·T² (those involving M, the Sun’s anomaly) are flagged in the table by an ePow column.

Accuracy: ~5 arcmin on (λ, β) and a few hundred km on Δ across decades — well inside the ~1.5° latitude window that determines whether a lunation produces an eclipse, and within ~1% of the published 356 000 – 407 000 km perigee/apogee bounds. Coefficients in src/data/moon.js, evaluator lunarPosition(jd) in src/physics/moon.js.

solar-system.html’s Moon uses lunarPosition directly: longitude and latitude both feed the schematic position so eclipse instants visibly collapse to the Sun–Earth axis (β ≈ 0) while ordinary new/full moons sit visibly above or below it (|β| ~ a few degrees). Distance stays at the schematic Earth–Moon distance for visibility — true distance is invisible at this view’s compression.

Eclipse detection

A lunar eclipse happens at full moon when the Moon enters Earth’s shadow cone; a solar eclipse happens at new moon when the Moon crosses the Sun-Earth line. Both reduce to the same geometric question: is the Moon’s ecliptic latitude small enough at this lunation?

nextLunarEclipse(jd) and nextSolarEclipse(jd) in src/physics/events.js walk lunations using the closed-form nextLunarPhase scanner, then for each candidate:

  1. Refine the mean-longitude new/full estimate to true-ephemeris accuracy by Newton iteration on (λ_moon − (λ_earth_helio + π)). Three to four steps; per-step clamp prevents a Newton jump from crossing a neighbouring lunation.
  2. Reject the lunation if |β| > 1.5° — the Moon misses the shadow / Sun-disc cone entirely. The 1.5° threshold is generous: it covers the penumbra geometry for lunar eclipses (Meeus §54) and the Moon-disc + Sun-disc + solar-parallax window for solar eclipses (Meeus §51).

Accuracy: matches NASA’s Five-Millennium Catalog of Solar / Lunar Eclipses within ~1 hour for the 2017–2024 reference set (test/eclipses.test.js). The error budget is dominated by truncating Σ_l / Σ_b to six terms — adding more terms would cut it to seconds, but the visualisation-only feature does not require it.

Solar declination and seasons (#82)

The latitude on Earth where the Sun is directly overhead at local noon — the solar declination δ — is a closed-form expression of Earth’s heliocentric longitude:

δ = arcsin(sin(ε) · sin(L_sun))

where ε = Earth’s obliquity (~23.44°, stored as axialTiltRad in src/data/planets.js) and L_sun is the Sun’s geocentric ecliptic longitude (Earth’s heliocentric longitude + π, wrapped to [0, 2π)).

δ oscillates between ±ε across a year. Implemented in src/physics/seasons.js.

The four cardinal solar events (March equinox, June solstice, September equinox, December solstice) are when L_sun crosses 0, π/2, π, 3π/2 respectively. Found by Newton iteration on the longitude residual in src/physics/events.js (nextSolarEvent), mirroring nextLunarPhase. Accuracy: better than ~0.01° / a few minutes within the J2000-fit window (1800–2050).

Season name at any JD is the one that started at the most recent cardinal event (Northern-hemisphere convention). The Southern hemisphere sees the inverted season (handled in the UI tooltip, not the physics).

Equilibrium tides (src/physics/tides.js)

The visualization uses the second-degree zonal term of the lunar + solar tidal potential:

h(rHat; moonHat, sunHat) = (3·(rHat·moonHat)² − 1) / 2  +  k_S · (3·(rHat·sunHat)² − 1) / 2

with k_S = M_sun · r_moon³ / (M_moon · r_sun³) ≈ 0.46 — the ratio of solar to lunar tidal force. This is a unit-amplitude shape function; the renderer multiplies by a tunable display constant (TIDE_DISPLAY_AMPLIFICATION) to exaggerate the millimetre-scale real bulges into something visible at the display scale of the Earth mesh.

Real equilibrium tides are ~50 cm (lunar) and ~20 cm (solar). Coastal tides diverge from the equilibrium model because of basin shape, resonance, Coriolis, and friction — the readout’s tooltip says so. This is a teaching diorama, not a tide-prediction tool.

The two-bulge geometry follows from the gradient-of-potential model: the cos² term peaks at both rHat·moonHat = +1 (sub-lunar) and rHat·moonHat = −1 (antipodal). A naïve “pull-toward-Moon” force model produces a single bulge and is wrong.

For the on-surface force arrows, the renderer uses the gradient form a = 3·(rHat·bodyHat)·bodyHat − rHat projected onto the local tangent plane (the part perpendicular to rHat). This horizontal component is what physically drives water flow; it vanishes at the sub-body point and the antipodal point and peaks at ~45° in the rHat-bodyHat plane.

← Astrarium