infinite-agents-public/specs/earth_orbit_simulator.md

33 KiB
Raw Blame History

Earth Orbit Simulator - Astronomical Accuracy Specification

Core Challenge

Create a scientifically accurate Three.js simulation of Earth's orbit around the Sun with precise astronomical parameters including obliquity, eccentricity, precession, rotation, and accurate solar illumination. The simulation must include time controls to visualize Earth's position and orientation at any point in time.

Project Overview

Build a single-file HTML application that demonstrates:

  • Accurate Orbital Mechanics: Kepler's laws, elliptical orbit with correct eccentricity
  • Earth's Rotation: Sidereal day (23h 56m 4.0916s) at accurate rate
  • Axial Tilt (Obliquity): ~23.44° (precisely 23.4392811° for J2000.0 epoch)
  • Orbital Eccentricity: ~0.0167 (Earth's orbit is slightly elliptical)
  • Axial Precession: ~26,000 year cycle (precession of the equinoxes)
  • Realistic Lighting: Sun as light source with accurate Earth day/night terminator
  • Time Simulation: Ability to speed up/slow down/reverse time
  • Interactive Controls: Slider to jump to any date/time

Output Requirements

File Naming: earth_orbit_simulator.html

Content Structure: Self-contained HTML file with astronomical simulation

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Earth Orbit Simulator - Astronomical Accuracy</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            font-family: 'Courier New', monospace;
            background: #000000;
        }
        canvas {
            display: block;
            width: 100vw;
            height: 100vh;
        }
        #info-panel {
            position: absolute;
            top: 10px;
            right: 10px;
            background: rgba(0, 0, 0, 0.85);
            color: #00ff00;
            padding: 20px;
            border-radius: 8px;
            font-size: 13px;
            min-width: 300px;
            border: 1px solid #00ff00;
            font-family: 'Courier New', monospace;
        }
        #time-controls {
            position: absolute;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 0, 0, 0.85);
            padding: 20px 30px;
            border-radius: 8px;
            border: 1px solid #00ff00;
            min-width: 600px;
        }
        .control-group {
            margin: 10px 0;
        }
        .control-group label {
            color: #00ff00;
            display: block;
            margin-bottom: 5px;
            font-size: 12px;
        }
        .control-group input[type="range"] {
            width: 100%;
            margin: 5px 0;
        }
        .button-group {
            display: flex;
            gap: 10px;
            margin-top: 15px;
        }
        button {
            background: #003300;
            color: #00ff00;
            border: 1px solid #00ff00;
            padding: 8px 16px;
            cursor: pointer;
            border-radius: 4px;
            font-family: 'Courier New', monospace;
        }
        button:hover {
            background: #005500;
        }
        button.active {
            background: #00ff00;
            color: #000000;
        }
        .data-row {
            display: flex;
            justify-content: space-between;
            margin: 5px 0;
            padding: 5px 0;
            border-bottom: 1px solid #003300;
        }
        .data-label {
            color: #00aa00;
        }
        .data-value {
            color: #00ff00;
            font-weight: bold;
        }
    </style>
</head>
<body>
    <div id="info-panel">
        <h3 style="margin: 0 0 15px 0; color: #00ff00;">EARTH ORBITAL DATA</h3>
        <div class="data-row">
            <span class="data-label">Current Date/Time:</span>
            <span class="data-value" id="current-time">-</span>
        </div>
        <div class="data-row">
            <span class="data-label">Julian Date:</span>
            <span class="data-value" id="julian-date">-</span>
        </div>
        <div class="data-row">
            <span class="data-label">Days since J2000:</span>
            <span class="data-value" id="days-j2000">-</span>
        </div>
        <div class="data-row">
            <span class="data-label">Rotation Angle:</span>
            <span class="data-value" id="rotation-angle">-</span>
        </div>
        <div class="data-row">
            <span class="data-label">Axial Tilt:</span>
            <span class="data-value" id="axial-tilt">23.4393°</span>
        </div>
        <div class="data-row">
            <span class="data-label">Orbital Position:</span>
            <span class="data-value" id="orbital-position">-</span>
        </div>
        <div class="data-row">
            <span class="data-label">Distance from Sun:</span>
            <span class="data-value" id="sun-distance">-</span>
        </div>
        <div class="data-row">
            <span class="data-label">Orbital Velocity:</span>
            <span class="data-value" id="orbital-velocity">-</span>
        </div>
        <div class="data-row">
            <span class="data-label">Precession Angle:</span>
            <span class="data-value" id="precession-angle">-</span>
        </div>
        <div class="data-row">
            <span class="data-label">Season:</span>
            <span class="data-value" id="season">-</span>
        </div>
    </div>

    <div id="time-controls">
        <div class="control-group">
            <label>Time Travel (Date/Time)</label>
            <input type="datetime-local" id="date-picker" />
        </div>
        <div class="control-group">
            <label>Time Speed: <span id="speed-value">1x Real-time</span></label>
            <input type="range" id="time-speed" min="-1000000" max="1000000" value="0" step="100" />
            <div style="display: flex; justify-content: space-between; font-size: 10px; color: #00aa00; margin-top: 5px;">
                <span>← 1M days/sec</span>
                <span>Paused</span>
                <span>1M days/sec →</span>
            </div>
        </div>
        <div class="button-group">
            <button id="btn-reverse">◄◄ Reverse</button>
            <button id="btn-slower">◄ Slower</button>
            <button id="btn-pause" class="active">⏸ Pause</button>
            <button id="btn-faster">Faster ►</button>
            <button id="btn-forward">Forward ►►</button>
            <button id="btn-reset">↺ Reset to Now</button>
        </div>
    </div>

    <script type="importmap">
    {
        "imports": {
            "three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
            "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
        }
    }
    </script>

    <script type="module">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

        // Scene, camera, renderer setup
        let camera, scene, renderer, controls;
        let sun, earth, earthOrbitLine;
        let earthRotationGroup, earthTiltGroup;

        // Astronomical constants (J2000.0 epoch)
        const ASTRONOMICAL_CONSTANTS = {
            // Earth orbital parameters
            SEMI_MAJOR_AXIS: 149.598e6,        // km (1 AU)
            ECCENTRICITY: 0.0167086,            // Orbital eccentricity
            OBLIQUITY: 23.4392811,              // Axial tilt in degrees (J2000)
            SIDEREAL_YEAR: 365.256363004,       // days
            SIDEREAL_DAY: 0.99726968,           // days (23h 56m 4.0916s)
            PRECESSION_PERIOD: 25772,           // years (axial precession)

            // Orbital elements (J2000.0)
            PERIHELION: 102.94719,              // Longitude of perihelion (degrees)
            MEAN_LONGITUDE: 100.46435,          // Mean longitude at epoch (degrees)

            // Scale for visualization (not to real scale)
            SCALE_DISTANCE: 100,                // Scale factor for distances
            SCALE_SIZE: 1,                      // Scale factor for body sizes

            // J2000.0 epoch
            J2000: 2451545.0,                   // Julian date of J2000.0 epoch (Jan 1, 2000, 12:00 TT)
        };

        // Simulation state
        let simulationTime = new Date();       // Current simulation time
        let timeSpeed = 0;                      // Time multiplier (0 = paused)
        let lastFrameTime = performance.now();

        init();
        animate();

        function init() {
            // Camera setup
            camera = new THREE.PerspectiveCamera(
                45,
                window.innerWidth / window.innerHeight,
                0.1,
                10000
            );
            camera.position.set(0, 150, 250);

            // Scene
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x000000);

            // Renderer
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.shadowMap.enabled = true;
            renderer.shadowMap.type = THREE.PCFSoftShadowMap;
            document.body.appendChild(renderer.domElement);

            // OrbitControls
            controls = new OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.05;
            controls.minDistance = 10;
            controls.maxDistance = 1000;

            // Create solar system
            createSolarSystem();

            // Setup UI controls
            setupControls();

            // Add starfield background
            createStarfield();

            // Handle resize
            window.addEventListener('resize', onWindowResize);

            // Initialize to current date/time
            resetToNow();
        }

        function createSolarSystem() {
            // Sun (light source)
            const sunGeometry = new THREE.SphereGeometry(10, 64, 64);
            const sunMaterial = new THREE.MeshBasicMaterial({
                color: 0xffff00,
                emissive: 0xffff00,
                emissiveIntensity: 1
            });
            sun = new THREE.Mesh(sunGeometry, sunMaterial);
            scene.add(sun);

            // Sun point light (primary light source)
            const sunLight = new THREE.PointLight(0xffffff, 2, 0);
            sunLight.castShadow = true;
            sunLight.shadow.mapSize.width = 2048;
            sunLight.shadow.mapSize.height = 2048;
            sun.add(sunLight);

            // Earth orbital path (ellipse)
            createEarthOrbit();

            // Earth group hierarchy for proper rotation and tilt
            // Structure: earthTiltGroup -> earthRotationGroup -> earth
            earthTiltGroup = new THREE.Group();
            scene.add(earthTiltGroup);

            earthRotationGroup = new THREE.Group();
            earthTiltGroup.add(earthRotationGroup);

            // Earth sphere with texture
            const earthGeometry = new THREE.SphereGeometry(5, 64, 64);

            // Load Earth texture
            const textureLoader = new THREE.TextureLoader();
            const earthTexture = textureLoader.load(
                'https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/planets/earth_atmos_2048.jpg'
            );
            const earthBumpMap = textureLoader.load(
                'https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/planets/earth_normal_2048.jpg'
            );

            const earthMaterial = new THREE.MeshPhongMaterial({
                map: earthTexture,
                bumpMap: earthBumpMap,
                bumpScale: 0.05,
                specular: 0x333333,
                shininess: 5
            });

            earth = new THREE.Mesh(earthGeometry, earthMaterial);
            earth.receiveShadow = true;
            earth.castShadow = true;
            earthRotationGroup.add(earth);

            // Set axial tilt
            earthTiltGroup.rotation.z = THREE.MathUtils.degToRad(ASTRONOMICAL_CONSTANTS.OBLIQUITY);

            // Add atmosphere glow
            const atmosphereGeometry = new THREE.SphereGeometry(5.2, 64, 64);
            const atmosphereMaterial = new THREE.MeshBasicMaterial({
                color: 0x6699ff,
                transparent: true,
                opacity: 0.15,
                side: THREE.BackSide
            });
            const atmosphere = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial);
            earth.add(atmosphere);
        }

        function createEarthOrbit() {
            // Create elliptical orbit path
            const orbitPoints = [];
            const segments = 360;
            const a = ASTRONOMICAL_CONSTANTS.SEMI_MAJOR_AXIS / ASTRONOMICAL_CONSTANTS.SCALE_DISTANCE;
            const e = ASTRONOMICAL_CONSTANTS.ECCENTRICITY;
            const b = a * Math.sqrt(1 - e * e); // Semi-minor axis

            for (let i = 0; i <= segments; i++) {
                const angle = (i / segments) * Math.PI * 2;
                const r = (a * (1 - e * e)) / (1 + e * Math.cos(angle));
                const x = r * Math.cos(angle);
                const z = r * Math.sin(angle);
                orbitPoints.push(new THREE.Vector3(x, 0, z));
            }

            const orbitGeometry = new THREE.BufferGeometry().setFromPoints(orbitPoints);
            const orbitMaterial = new THREE.LineBasicMaterial({
                color: 0x00ff00,
                opacity: 0.3,
                transparent: true
            });
            earthOrbitLine = new THREE.Line(orbitGeometry, orbitMaterial);
            scene.add(earthOrbitLine);
        }

        function createStarfield() {
            const starsGeometry = new THREE.BufferGeometry();
            const starCount = 5000;
            const positions = new Float32Array(starCount * 3);

            for (let i = 0; i < starCount * 3; i += 3) {
                const theta = Math.random() * Math.PI * 2;
                const phi = Math.acos(Math.random() * 2 - 1);
                const r = 500 + Math.random() * 500;

                positions[i] = r * Math.sin(phi) * Math.cos(theta);
                positions[i + 1] = r * Math.sin(phi) * Math.sin(theta);
                positions[i + 2] = r * Math.cos(phi);
            }

            starsGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

            const starsMaterial = new THREE.PointsMaterial({
                color: 0xffffff,
                size: 0.7,
                transparent: true,
                opacity: 0.8
            });

            const stars = new THREE.Points(starsGeometry, starsMaterial);
            scene.add(stars);
        }

        function setupControls() {
            const datePicker = document.getElementById('date-picker');
            const timeSpeedSlider = document.getElementById('time-speed');
            const speedValue = document.getElementById('speed-value');

            // Date picker
            datePicker.addEventListener('change', (e) => {
                simulationTime = new Date(e.target.value);
                updateSimulation();
            });

            // Time speed slider
            timeSpeedSlider.addEventListener('input', (e) => {
                timeSpeed = parseFloat(e.target.value);
                updateSpeedDisplay();
            });

            // Buttons
            document.getElementById('btn-reverse').addEventListener('click', () => {
                timeSpeed = -86400; // -1 day per second
                updateSpeedDisplay();
            });

            document.getElementById('btn-slower').addEventListener('click', () => {
                timeSpeed = Math.max(timeSpeed / 2, -1000000);
                timeSpeedSlider.value = timeSpeed;
                updateSpeedDisplay();
            });

            document.getElementById('btn-pause').addEventListener('click', () => {
                timeSpeed = 0;
                timeSpeedSlider.value = 0;
                updateSpeedDisplay();
            });

            document.getElementById('btn-faster').addEventListener('click', () => {
                timeSpeed = Math.min(timeSpeed === 0 ? 1 : timeSpeed * 2, 1000000);
                timeSpeedSlider.value = timeSpeed;
                updateSpeedDisplay();
            });

            document.getElementById('btn-forward').addEventListener('click', () => {
                timeSpeed = 86400; // +1 day per second
                updateSpeedDisplay();
            });

            document.getElementById('btn-reset').addEventListener('click', resetToNow);
        }

        function resetToNow() {
            simulationTime = new Date();
            timeSpeed = 0;
            document.getElementById('time-speed').value = 0;
            updateSpeedDisplay();
            updateSimulation();
        }

        function updateSpeedDisplay() {
            const speedValue = document.getElementById('speed-value');
            if (timeSpeed === 0) {
                speedValue.textContent = 'Paused';
            } else if (Math.abs(timeSpeed) < 1) {
                speedValue.textContent = `${timeSpeed.toFixed(3)}x Real-time`;
            } else if (Math.abs(timeSpeed) < 86400) {
                speedValue.textContent = `${(timeSpeed / 3600).toFixed(1)} hours/sec`;
            } else {
                speedValue.textContent = `${(timeSpeed / 86400).toFixed(1)} days/sec`;
            }
        }

        function calculateOrbitalPosition(julianDate) {
            // Calculate days since J2000.0 epoch
            const d = julianDate - ASTRONOMICAL_CONSTANTS.J2000;

            // Mean anomaly (degrees)
            const M = ASTRONOMICAL_CONSTANTS.MEAN_LONGITUDE +
                     (360.0 / ASTRONOMICAL_CONSTANTS.SIDEREAL_YEAR) * d -
                     ASTRONOMICAL_CONSTANTS.PERIHELION;

            // Solve Kepler's equation for eccentric anomaly (iterative)
            let E = THREE.MathUtils.degToRad(M);
            const e = ASTRONOMICAL_CONSTANTS.ECCENTRICITY;
            for (let i = 0; i < 10; i++) {
                E = E - (E - e * Math.sin(E) - THREE.MathUtils.degToRad(M)) / (1 - e * Math.cos(E));
            }

            // True anomaly
            const v = 2 * Math.atan2(
                Math.sqrt(1 + e) * Math.sin(E / 2),
                Math.sqrt(1 - e) * Math.cos(E / 2)
            );

            // Distance from sun
            const r = ASTRONOMICAL_CONSTANTS.SEMI_MAJOR_AXIS * (1 - e * Math.cos(E));

            // Position in orbital plane
            const x = (r / ASTRONOMICAL_CONSTANTS.SCALE_DISTANCE) * Math.cos(v);
            const z = (r / ASTRONOMICAL_CONSTANTS.SCALE_DISTANCE) * Math.sin(v);

            return { x, z, r, v: THREE.MathUtils.radToDeg(v), d };
        }

        function updateSimulation() {
            // Convert to Julian Date
            const jd = dateToJulianDate(simulationTime);

            // Calculate orbital position
            const orbital = calculateOrbitalPosition(jd);

            // Update Earth position
            earthTiltGroup.position.set(orbital.x, 0, orbital.z);

            // Calculate Earth rotation (sidereal day)
            const daysSinceJ2000 = jd - ASTRONOMICAL_CONSTANTS.J2000;
            const rotations = daysSinceJ2000 / ASTRONOMICAL_CONSTANTS.SIDEREAL_DAY;
            earthRotationGroup.rotation.y = (rotations % 1) * Math.PI * 2;

            // Calculate precession (very slow, ~26,000 year cycle)
            const precessionAngle = (daysSinceJ2000 / (ASTRONOMICAL_CONSTANTS.PRECESSION_PERIOD * 365.25)) * 360;

            // Update UI
            updateUI(jd, orbital, daysSinceJ2000, rotations, precessionAngle);

            // Update date picker
            const dateString = simulationTime.toISOString().slice(0, 16);
            document.getElementById('date-picker').value = dateString;
        }

        function updateUI(jd, orbital, daysSinceJ2000, rotations, precessionAngle) {
            document.getElementById('current-time').textContent =
                simulationTime.toUTCString();
            document.getElementById('julian-date').textContent =
                jd.toFixed(2);
            document.getElementById('days-j2000').textContent =
                daysSinceJ2000.toFixed(2);
            document.getElementById('rotation-angle').textContent =
                ((rotations % 1) * 360).toFixed(2) + '°';
            document.getElementById('orbital-position').textContent =
                orbital.v.toFixed(2) + '°';
            document.getElementById('sun-distance').textContent =
                (orbital.r / 1e6).toFixed(3) + ' million km';
            document.getElementById('precession-angle').textContent =
                (precessionAngle % 360).toFixed(2) + '°';

            // Calculate orbital velocity (simplified)
            const velocity = Math.sqrt(
                1.327e20 * (2 / orbital.r - 1 / ASTRONOMICAL_CONSTANTS.SEMI_MAJOR_AXIS)
            ) / 1000;
            document.getElementById('orbital-velocity').textContent =
                velocity.toFixed(2) + ' km/s';

            // Determine season (Northern Hemisphere)
            const season = getSeason(orbital.v);
            document.getElementById('season').textContent = season;
        }

        function getSeason(orbitalAngle) {
            // Approximate seasons based on orbital position
            // 0° = Perihelion (early January)
            const adjusted = (orbitalAngle + 12) % 360; // Adjust for season alignment
            if (adjusted < 90) return 'Winter (N) / Summer (S)';
            if (adjusted < 180) return 'Spring (N) / Autumn (S)';
            if (adjusted < 270) return 'Summer (N) / Winter (S)';
            return 'Autumn (N) / Spring (S)';
        }

        function dateToJulianDate(date) {
            return (date.getTime() / 86400000) + 2440587.5;
        }

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        function animate() {
            requestAnimationFrame(animate);

            const currentTime = performance.now();
            const deltaTime = (currentTime - lastFrameTime) / 1000; // seconds
            lastFrameTime = currentTime;

            // Update simulation time based on speed
            if (timeSpeed !== 0) {
                simulationTime = new Date(simulationTime.getTime() + (timeSpeed * deltaTime * 1000));
                updateSimulation();
            }

            controls.update();
            renderer.render(scene, camera);
        }
    </script>
</body>
</html>

Astronomical Accuracy Requirements

1. Orbital Mechanics (Kepler's Laws)

Elliptical Orbit:

  • Semi-major axis: 149.598 million km (1 AU)
  • Eccentricity: 0.0167086
  • Perihelion: ~147.1 million km (early January)
  • Aphelion: ~152.1 million km (early July)

Orbital Period:

  • Sidereal year: 365.256363004 days
  • Tropical year: 365.24219 days

Mathematical Implementation:

  • Use Kepler's equation to solve for position: M = E - e·sin(E)
  • Calculate true anomaly from eccentric anomaly
  • Convert to Cartesian coordinates in orbital plane

2. Earth's Rotation

Rotational Period:

  • Sidereal day: 23h 56m 4.0916s (0.99726968 days)
  • NOT 24 hours (that's solar day)

Implementation:

  • Rotation angle = (days since epoch / sidereal day) × 360°
  • Rotate Earth mesh around Y-axis

3. Axial Tilt (Obliquity)

Current Value:

  • 23.4392811° (J2000.0 epoch reference)
  • Changes slowly over time (~23.1° to 24.5° over 41,000 years)

Implementation:

  • Apply tilt to Earth's rotation group
  • Tilt is relative to orbital plane normal
  • Keep tilt direction fixed in space (relative to stars)

4. Axial Precession

Precession Period:

  • ~25,772 years (precession of the equinoxes)
  • Earth's axis traces a cone in space

Implementation:

  • Very slow wobble of tilt direction
  • Barely noticeable in simulation unless sped up significantly
  • Affects which star is the "North Star" over millennia

5. Realistic Lighting

Sun as Point Light:

  • Position at origin (0, 0, 0)
  • Intensity sufficient to light Earth realistically
  • Enable shadows for accurate terminator

Day/Night Terminator:

  • Should be perpendicular to Sun-Earth line
  • Proper shadow mapping on Earth surface
  • Atmospheric scattering effect (optional enhancement)

UI/UX Requirements

Information Panel (Top Right)

Display real-time astronomical data:

  • Current Date/Time: UTC format
  • Julian Date: Astronomical time standard
  • Days since J2000: Days since January 1, 2000, 12:00 TT
  • Rotation Angle: Current rotation in degrees
  • Axial Tilt: 23.4393° (display constant)
  • Orbital Position: Angle from perihelion in degrees
  • Distance from Sun: Current distance in million km
  • Orbital Velocity: Current orbital speed in km/s
  • Precession Angle: Current precession phase
  • Season: Northern/Southern hemisphere season

Time Controls (Bottom)

Date/Time Picker:

  • Jump to any specific date/time
  • Use HTML5 <input type="datetime-local">

Time Speed Slider:

  • Range: -1,000,000 to +1,000,000 (days per second)
  • Center (0): Paused
  • Negative: Reverse time
  • Positive: Forward time
  • Display current speed multiplier

Quick Control Buttons:

  • Reverse: Go backwards at 1 day/second
  • Slower: Halve current speed
  • Pause: Stop time (speed = 0)
  • Faster: Double current speed
  • Forward: Go forward at 1 day/second
  • Reset: Return to current real-world time

Technical Implementation

Option 1: astronomy-engine (NPM package)

// Highly accurate astronomical calculations
// https://github.com/cosinekitty/astronomy
import * as Astronomy from 'https://cdn.jsdelivr.net/npm/astronomy-engine@2.1.19/+esm'

// Get Earth's position at specific time
const time = new Astronomy.AstroTime(new Date());
const earthPos = Astronomy.HelioVector(Astronomy.Body.Earth, time);

Pros:

  • Extremely accurate (JPL ephemeris quality)
  • Easy to use
  • Handles all orbital mechanics automatically
// Implement Kepler's laws manually
// Uses mean orbital elements
function calculateOrbitalPosition(date) {
    // 1. Calculate Julian Date
    // 2. Days since J2000 epoch
    // 3. Mean anomaly = mean longitude - longitude of perihelion
    // 4. Solve Kepler's equation iteratively for eccentric anomaly
    // 5. Calculate true anomaly
    // 6. Convert to Cartesian coordinates
    // 7. Apply orbital plane inclination (0° for Earth)
}

Pros:

  • Complete control
  • Educational value
  • No external dependencies beyond Three.js

Option 3: NASA JPL Horizons API

// Query NASA's HORIZONS system for precise ephemeris
// https://ssd.jpl.nasa.gov/api/horizons.api
fetch('https://ssd.jpl.nasa.gov/api/horizons.api?...')

Pros:

  • Most accurate possible
  • Official NASA data

Cons:

  • Requires API calls (network dependency)
  • Rate limited
  • More complex setup

Coordinate Systems

Three.js Scene:

  • Sun at origin (0, 0, 0)
  • Orbital plane in XZ plane (Y = 0)
  • +X axis: Vernal equinox direction
  • +Y axis: North ecliptic pole
  • +Z axis: 90° from vernal equinox

Earth Orientation:

  • Use nested groups: Sun → Orbit → Tilt → Rotation → Earth
  • Orbit group: position along orbital path
  • Tilt group: apply 23.44° tilt around Z-axis
  • Rotation group: rotate around Y-axis (Earth's axis)

Texture Resources

Earth Textures:

  • Day texture: earth_atmos_2048.jpg or earth_daymap_4096.jpg
  • Night texture (optional): earth_nightmap_2048.jpg
  • Normal/bump map: earth_normal_2048.jpg
  • Specular map (optional): earth_specular_2048.jpg

Sources:

  • Three.js examples: https://github.com/mrdoob/three.js/tree/dev/examples/textures/planets
  • NASA Visible Earth: https://visibleearth.nasa.gov/
  • Solar System Scope: https://www.solarsystemscope.com/textures/

Quality Standards

Astronomical Accuracy

Required Precision:

  • Orbital position: ±0.1° accuracy
  • Rotation angle: ±1° accuracy
  • Axial tilt: ±0.01° accuracy
  • Distance from Sun: ±0.1 million km

Validation Methods:

  • Compare with JPL Horizons data for specific dates
  • Verify solstice/equinox dates align with real dates
  • Check perihelion/aphelion dates (Jan 3 / July 4)
  • Validate orbital velocity at known points

Performance

Target:

  • 60fps smooth animation
  • Responsive time controls (no lag)
  • Smooth camera controls
  • Fast date jumping (no recalculation delay)

Visual Quality

Required:

  • Realistic Earth texture with good resolution
  • Accurate day/night terminator
  • Smooth lighting falloff
  • No visual artifacts or popping
  • Clean, readable UI

Optional Enhancements:

  • Atmospheric glow around Earth
  • Clouds layer (animated)
  • City lights on night side
  • Lens flare from Sun
  • Orbital trail visualization

Advanced Features (Optional)

1. Multi-Body System

  • Add Moon with accurate orbit
  • Add other planets
  • Show relative positions

2. Enhanced Accuracy

  • Nutation (nodding motion)
  • Lunar perturbations
  • Gravitational effects of other planets
  • General relativity corrections

3. Educational Features

  • Highlight equinoxes and solstices
  • Show tropics and polar circles
  • Indicate subsolar point
  • Display constellation background
  • Show celestial equator

4. Data Visualization

  • Plot orbital velocity over time
  • Graph distance from Sun
  • Show axial tilt variation over millennia
  • Display Earth-Sun-Moon geometry

Testing & Validation

Test Cases

Known Astronomical Events:

  1. March Equinox 2024: March 20, 03:06 UTC

    • Earth at ~90° from perihelion
    • Day/night equal length
  2. June Solstice 2024: June 20, 20:51 UTC

    • Earth at ~180° from perihelion (aphelion nearby)
    • Maximum northern tilt toward Sun
  3. September Equinox 2024: September 22, 12:44 UTC

    • Earth at ~270° from perihelion
    • Day/night equal length
  4. December Solstice 2024: December 21, 09:21 UTC

    • Earth near perihelion
    • Maximum southern tilt toward Sun

Validation Procedure:

  1. Set simulation to test date
  2. Verify orbital position matches expected angle
  3. Check tilt direction relative to Sun
  4. Validate distance from Sun
  5. Confirm season displayed correctly

Comparison Sources

Official Ephemeris:

  • NASA JPL Horizons: https://ssd.jpl.nasa.gov/horizons/
  • US Naval Observatory: https://aa.usno.navy.mil/data/
  • IAU SOFA: http://www.iausofa.org/

Success Criteria

A successful Earth orbit simulator must:

  1. Accurate Orbital Motion: Earth follows elliptical path with correct eccentricity
  2. Correct Rotation: Sidereal day (23h 56m 4s), not solar day
  3. Precise Tilt: 23.44° axial tilt maintained relative to stars
  4. Realistic Lighting: Day/night terminator perpendicular to Sun direction
  5. Time Control: Smooth forward/reverse/pause/jump controls
  6. Live Data Display: All orbital parameters updated in real-time
  7. Validation: Matches known astronomical events (solstices, equinoxes)
  8. Performance: 60fps with smooth animations
  9. Self-Contained: Single HTML file, works offline after initial load
  10. Educational Value: Clearly demonstrates astronomical concepts

Reference Implementation Notes

Kepler's Equation Solver

function solveKeplerEquation(M, e, iterations = 10) {
    // M = mean anomaly (radians)
    // e = eccentricity
    // Solve: M = E - e·sin(E) for E (eccentric anomaly)

    let E = M; // Initial guess
    for (let i = 0; i < iterations; i++) {
        E = E - (E - e * Math.sin(E) - M) / (1 - e * Math.cos(E));
    }
    return E;
}

function eccentricToTrueAnomaly(E, e) {
    // Convert eccentric anomaly to true anomaly
    return 2 * Math.atan2(
        Math.sqrt(1 + e) * Math.sin(E / 2),
        Math.sqrt(1 - e) * Math.cos(E / 2)
    );
}

Julian Date Conversion

function dateToJulianDate(date) {
    // Convert JavaScript Date to Julian Date
    return (date.getTime() / 86400000) + 2440587.5;
}

function julianDateToDate(jd) {
    // Convert Julian Date to JavaScript Date
    return new Date((jd - 2440587.5) * 86400000);
}

Documentation Requirements

The simulation should include:

  • Comments explaining astronomical concepts
  • References to equations used
  • Links to validation sources
  • Instructions for use in info panel

Future Enhancements

Potential additions for advanced versions:

  • Apsidal precession (orbital ellipse rotation)
  • Milankovitch cycles visualization
  • Historical Earth positions (dinosaur era, ice ages)
  • Comparison with other planets
  • Accurate Moon system
  • Eclipse prediction
  • Satellite tracking

Generate an astronomically accurate, interactive Earth orbit simulator that demonstrates the beauty of celestial mechanics and serves as both a scientific tool and educational resource.