# @essrt/physics

The physics core of **ESSRT** — the Expanding Solar System Resonance Theory —
as implemented by the [ESSRT simulator](https://3d.holisticuniverse.com)
([source, AGPL-3.0](https://github.com/dvansonsbeek/3d)). Pure computation:
no I/O, no globals, no DOM, constants injected. The package ships the
**complete model**, including the fitted coefficients, so an installed copy
reproduces the simulator's engine on the shared golden masters (bit-exact in
Node; the browser build is held to a measured cross-runtime tolerance).

## Versioning — two axes, deliberately

- **Package semver** (this package's `version`) tracks the **API axis** —
  what `import` gives you.
- **Model identity** rides *inside* every published version: the
  `essrt.modelVersion` field in `package.json` plus `CONSTANTS_HASH` and
  `COEFFICIENTS_HASH` in `src/constants/`. A refit or structural model
  change always lands as a new package version — npm's immutability then
  guarantees a pinned version's results never change. The hashes cover
  VALUES: the artifacts' provenance stamps (their `inputs` blocks) are
  excluded, so a re-stamp that moves no number moves no hash.

Cite results as *model vX.Y (package A.B.C)*.

## Usage

```js
import { DEFAULT_CONSTANTS, FITTED_COEFFICIENTS, createEpochPrimitives } from '@essrt/physics';
```

Domain modules are CJS subpath exports, each a `create*` factory taking its
dependencies explicitly (the injectable-constants design — counterfactual
runs are first-class):

```js
const { createEclipseFinders } = require('@essrt/physics/eclipse/finders');
const { createMoonSeries } = require('@essrt/physics/moon/series');
```

**TypeScript**: the package ships declaration files (`types/`, one per
subpath export) generated from the sources' JSDoc — `import` from
`@essrt/physics` or any subpath is fully typed; no ambient shim needed.

See the `exports` map in `package.json` for the full surface: moon
(arguments, series, series-extension, apparent, ecc-channel,
deep-ecc-channel, month-chain), earth (year-lengths, precession-composed,
frame-of-date, sidereal-year-channel, deep-orbital-history), planets
(keplerian-chain, orbit-chain, secular-shape, secular-series,
inv-plane-frame, spin-channel, ecc-channel, geometry, model), deltat
(cycles, deep-time, moon-distance-history + moon-distance-artifact — the cited
AstroGeo22 Earth–Moon distance history, historical), the cardinal structure,
the deep calendar, chain-cycles, sun, climate and eclipse finders.

For rendered display values (day/year lengths, precession rates, orbital
elements as preformatted strings), see
[`@essrt/model-values`](https://www.npmjs.com/package/@essrt/model-values) —
generated from this engine at the same model version.

## The assembled model — `createModel()`

The canonical assembly, wired once inside the package:

```js
import { createModel, DEFAULT_CONSTANTS } from '@essrt/physics';

const model = createModel();
model.identity.modelVersion;          // the shipped model identity (e.g. 'v21.0') + both content hashes
model.lunisolar.meanPeriodYearsAtYear(2000 - 380e6); // 21699 — the Devonian precession period
model.epoch.lodSecondsAtYear(2000 - 380e6);          // 78895 — the Devonian day (Wells 1963's coral count)
model.earth.obliquityDeg(2000);       // 23.4393
model.cardinal.jd(2000, 'SS');        // 2451716.5751 — June solstice 2000, the apparent Sun's crossing (USNO 2451716.575)
model.lengths.tropicalYearDays(2000); // 365.24219

// Counterfactual (§2d): inject different constants, get a different solar
// system — self-identifying, with its own constants hash.
const cf = createModel({
  ...DEFAULT_CONSTANTS,
  earthOrbital: { ...DEFAULT_CONSTANTS.earthOrbital, obliquityJ2000_deg: 23.5 },
});
cf.earth.obliquityDeg(2000);          // 23.5 — the injected anchor read through the assembly
cf.identity.counterfactual;           // true
```

The surface groups: `time` (exact JD ↔ model-year conversion on the SI
axis the fits were anchored on — callers holding a JD convert here, never
with their own formula), `identity` (version + hashes + citation DOI), `epoch`
(the internal unit, LOD, α, ΔT, the precession period and the deep-time
quantities at any year ±500 Myr), `lunisolar` (the mean lunisolar precession
period and rate at any year, its solar/lunar torque split, the nodal mode
and the obliquity beat, the apsidal and perihelion-of-date periods and their
ratios over the published window), `earth`
(obliquity, eccentricity, inclination, perihelion), `lengths` (year and
day lengths), `cardinal` (solstice/equinox JD, RA, year length), `moon`
(distance and months at epoch, plus the apparent-position chain on the
JD(UT) axis — ecliptic longitude, latitude and distance from the shared
Meeus series over framework-native arguments), `eclipse` (geocentric
solar/lunar eclipse search over a JD window, greatest-eclipse convention),
`climate` (the L1 orbital-forcing formula) and `planets` (the N-body chain's
elements of date — perihelion longitude, node and inclination on the
invariable plane, eccentricity — and spin). Validation targets are refused as
inputs — a counterfactual cannot move the goalposts it is judged by.

### The series artifact

Earth's published orbit history (eccentricity, perihelion, obliquity) is
the engine's banked N-body series — a 9-MB governed artifact kept in the
[public repository](https://github.com/dvansonsbeek/3d)
(`data/nbody-secular-series.json`), not inside this package. Pass it in to
get the published values:

```js
const series = JSON.parse(readFileSync('nbody-secular-series.json', 'utf8'));
const model = createModel(undefined, { secularSeriesArtifact: series });
model.earth.eccentricity(2000 - 100000);   // 0.04008 — the series (La2004: 0.04006)
createModel().earth.eccentricity(2000 - 100000);   // 0.03374 — the mode table
```

`model.identity.earthElements` names which one a result came from
(`'secular-series'` or `'mode-table'`), so a stored value is never ambiguous.
Without the artifact `createModel()` evaluates the embedded 18-term mode
table at every epoch: exact at J2000, a compact approximation away from it
(RMS 3.6·10⁻³ in e against La2010a over the last 500 kyr, where the series
reads 2.5·10⁻⁵). The API handler, the MCP server and the simulator always
pass the artifact.

## Underneath: a parts library

`createModel()` is assembly over unassembled factories. Each factory
receives everything through one deps object — plain numbers plus
functions — and no module imports another; that is what makes
counterfactuals first-class and lets the same core run in Node and the
browser. The assembly itself is `src/model.js`, a composer of the named
slices in `src/model/` (one file per family of quantities —
`one-source-movement`, `lunar-theory`, `sun-and-eclipses`, `surface`, …),
so a quantity is found by its slice's filename. For custom wiring (different α channel, subset assembly), use
the factories directly: the reference adapter is `tools/lib/` in the
[public repository](https://github.com/dvansonsbeek/3d) — the hosted
simulator and the project's verification gates run on it. A service
surface over `createModel()` lives in the same repository
(`packages/api`, an OpenAPI 3.1 handler with zero runtime dependencies)
— build-only until the hosting decision.

## Provenance

Every value derives from the model parameters and fitted coefficients in
this package; the fitting pipeline, verification gates, dataset manifest and
documentation live in the [public repository](https://github.com/dvansonsbeek/3d).
Preprint: <https://doi.org/10.21203/rs.3.rs-8758810/v4>.

## Licence

AGPL-3.0-or-later. Commercial licensing: dennis@holisticuniverse.com.
