stellar.js
88 lines · in natura-src-1.0.0.zip as natura/stellar.js · a module · its entry in the module reference · its test: stellar-test.js
// stellar.js -- Natura STELLAR (SYSTEMS.md #11, ALGORITHMS.md): the deep-time energy source. A star's
// whole life told as a function of AGE in billions of years -- the slow main-sequence brightening, the
// swelling into a red giant, and the long cooling of the white-dwarf ember afterward. Everything is
// ANALYTIC in the clock time (no integration over a Gyr), so it is exact and resumable at any moment of
// the star's life. It exposes the star's luminosity, radius, temperature, and phase, and the INSOLATION
// at a planet's orbit -- which is the boundary condition the whole climate hangs from: a brightening sun
// marches the habitable zone outward (frozen -> temperate -> scorched over eons), and the biome system,
// reading that trend, shifts the living world to match. The cosmic rung of the scale ladder.
//
// Reads only the clock (at eon scale). Deterministic + node-testable. UMD (node test / window.Stellar).
(function (root, factory) {
if (typeof module !== 'undefined' && module.exports) module.exports = factory();
else root.Stellar = factory();
})(typeof self !== 'undefined' ? self : this, function () {
'use strict';
// stellar state at an age (Gyr), for a star of `mass` solar masses. Solar-normalized units
// (L_sun, R_sun, T in K with T_sun=5772). A compact but qualitatively faithful evolution track.
function evolve(ageGyr, mass, planetDist) {
const tMs = 10 / Math.pow(mass, 2.5); // main-sequence lifetime (~10 Gyr for the Sun)
const L0 = Math.pow(mass, 3.5); // zero-age main-sequence luminosity (mass-luminosity)
const tRg = 1.0 * Math.pow(mass, -1); // red-giant duration (~1 Gyr for the Sun)
let L, R, phase;
if (ageGyr < tMs) { // MAIN SEQUENCE: slow ~2x brightening over the life
L = L0 * (0.7 + 0.7 * (ageGyr / tMs));
R = Math.pow(mass, 0.8);
phase = 'main-sequence';
} else if (ageGyr < tMs + tRg) { // RED GIANT: L climbs ~1000x, the star swells hugely
const frac = (ageGyr - tMs) / tRg; // 0 -> 1 (the RGB tip)
const Lend = L0 * 1.4;
L = Lend * Math.pow(2000 / 1.4, frac);
R = Math.pow(mass, 0.8) * Math.pow(180, frac);
phase = 'red-giant';
} else { // WHITE DWARF: the ember, small + hot, cooling for Gyr
const tau = 0.3, aw = ageGyr - (tMs + tRg);
L = 1.0 * Math.pow(tau / (aw + tau), 1.3);
R = 0.013 * Math.pow(mass, -1 / 3); // ~Earth-sized, denser for more mass
phase = 'white-dwarf';
}
const T = 5772 * Math.pow(Math.max(1e-9, L) / (R * R), 0.25); // Stefan-Boltzmann: L = R^2 (T/Tsun)^4
const insol = L / (planetDist * planetDist); // insolation at the planet (present Sun ~ 1)
return { L, R, T, phase, insol };
}
// make a Stellar system. config: { mass(solar), age0Gyr(present age), planetDist(AU), name }.
function makeStellar(config) {
config = config || {};
const mass = config.mass != null ? config.mass : 1.0;
const age0 = config.age0Gyr != null ? config.age0Gyr : 4.6; // default: the Sun's present age
const planetDist = config.planetDist != null ? config.planetDist : 1.0;
return {
name: config.name || 'stellar',
domain: 'cosmic',
scale: 'stellar',
params: config,
init(rng, params, clock) {
const a0 = age0 + (clock ? clock.t / (1e9 * 365 * 86400) : 0);
const s = evolve(a0, mass, planetDist);
return { age: a0, cur: s, prevPhase: s.phase, peakL: s.L, firedPeak: false };
},
step(st, ctx, dt) {
const age = age0 + ctx.clock.t / (1e9 * 365 * 86400); // present age + elapsed, in Gyr
const s = evolve(age, mass, planetDist);
st.events = [];
if (s.phase !== st.prevPhase) {
if (s.phase === 'red-giant') st.events.push({ kind: 'leaveMainSequence', age: +age.toFixed(3) });
if (s.phase === 'white-dwarf') st.events.push({ kind: 'whiteDwarf', age: +age.toFixed(3) });
st.prevPhase = s.phase;
}
// the red-giant luminosity peak (the RGB tip), edge-triggered once
if (s.phase === 'red-giant') { if (s.L > st.peakL) st.peakL = s.L; }
else if (st.prevPhase === 'white-dwarf' && !st.firedPeak && st.peakL > 100) { st.events.push({ kind: 'redGiantPeak', peakL: +st.peakL.toFixed(0) }); st.firedPeak = true; }
st.age = age; st.cur = s;
return st;
},
// OUT: luminosity, radius, temperature, phase, and INSOLATION (the climate's deep-time boundary condition)
fields(st) { return { luminosity: st.cur.L, radius: st.cur.R, temperature: st.cur.T,
phase: st.cur.phase, insolation: st.cur.insol, ageGyr: st.age }; },
events(st) { return st.events || []; }
};
}
return { makeStellar, evolve };
});