solargod/js/scale.js
monsterrobotparty 26a5751394 🌞 genesis: scaffold — serve.py proxy+cache, importmap renderer, sky, sun, HUD, time bar, floating-origin bootstrap (opus)
Stage 0. Zero-dependency serve.py (static + allowlisted horizons/sbdb/cad proxy,
sha256 disk cache with past-span immutability). Three.js via pinned importmap,
logarithmic depth, procedural starfield sky (+ Milky Way panorama swap-in), Sun
with additive glow, OrbitControls, brass-and-void HUD, scrubbable time bar,
central bodyWorld position pass, focus/camera system, hash state. Boots clean,
no console errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 00:04:41 +10:00

85 lines
3.2 KiB
JavaScript

// SOLARGOD view transform — the one module that owns MEGA/TRUE compression and
// radius exaggeration. Truth (heliocentric AU) goes in, view-world units come
// out. Nothing else in the app maps AU→scene by hand (brief §4). "Truth before
// view": callers compute positions in AU, then pass through here.
import { AU_KM, eclToWorld, clamp, lerp, smoothstep } from './lib.js';
let K = 9.0;
let P = 0.4; // live exponent: MEGA=0.4, TRUE=1.0
let P_mega = 0.4, P_true = 1.0;
let planetE = 1200, sunE = 60;
let transitionMs = 2500;
let mode = 'mega';
let tween = null; // { from, to, elapsed } while animating
export function init(CONFIG) {
K = CONFIG.scale.K;
P_mega = CONFIG.scale.P_mega;
P_true = CONFIG.scale.P_true;
P = P_mega;
planetE = CONFIG.scale.planetE;
sunE = CONFIG.scale.sunE;
transitionMs = CONFIG.scale.transitionMs;
moonBase = CONFIG.scale.moonBase;
moonK = CONFIG.scale.moonK;
}
let moonBase = 1.8, moonK = 1.2;
// Radial compression: AU → view units. Direction is never touched, so MEGA↔TRUE
// preserves every body's heliocentric bearing (verify gate: unit dot ≈ 1).
export function radial(rAU) { return K * Math.pow(rAU, P); }
// Heliocentric ecliptic AU → view-world Vector3 (radial compression + frame map).
export function viewFromEcl(ecl, out) {
const r = Math.hypot(ecl.x, ecl.y, ecl.z);
if (r === 0) { out.x = out.y = out.z = 0; return out; }
const s = radial(r) / r; // scale factor along the true direction
eclToWorld({ x: ecl.x * s, y: ecl.y * s, z: ecl.z * s }, out);
return out;
}
// Body draw radius in view units. E defaults to the planet exaggeration; the Sun
// and close-focus lerp pass their own.
export function drawRadius(radiusKm, E = planetE) {
return (radiusKm / AU_KM) * K * E;
}
// Local moon distance from parent centre, view units. MEGA lifts every moon clear
// of the exaggerated parent surface and preserves ordering; TRUE is plain linear
// true distance. Blends with the same P animation.
export function moonDistance(dKm, parentRkm, parentDrawView) {
const megaD = parentDrawView * (moonBase + moonK * Math.log10(1 + dKm / parentRkm));
const trueD = (dKm / AU_KM) * K;
const blend = clamp((P - P_mega) / (P_true - P_mega), 0, 1);
return lerp(megaD, trueD, blend);
}
// ---- mode / transition ----
export function getMode() { return mode; }
export function getP() { return P; }
export function isTransitioning() { return tween !== null; }
export function setMode(next) {
if (next === mode && !tween) return;
mode = next;
tween = { from: P, to: next === 'true' ? P_true : P_mega, elapsed: 0 };
}
export function toggle() { setMode(mode === 'mega' ? 'true' : 'mega'); }
// Advance the MEGA↔TRUE tween. Call once per frame with wall dt (ms).
export function update(dtMs) {
if (!tween) return;
tween.elapsed += dtMs;
const t = clamp(tween.elapsed / transitionMs, 0, 1);
P = lerp(tween.from, tween.to, smoothstep(t));
if (t >= 1) { P = tween.to; tween = null; }
}
// ---- exaggeration (HUD slider) ----
export function setPlanetE(e) { planetE = e; }
export function getPlanetE() { return planetE; }
export function getSunE() { return sunE; }
export function getK() { return K; }