{"version":3,"sources":["seasons.mjs"],"sourcesContent":["// Kubicor — WORLD SEASONS, PHASE 1 (the owner's World Seasons plan, Oct 10): every world's axial tilt, its year, where\n// it is in that year now, and what that does to its daylight. SHARED byte-for-byte: public/seasons.mjs,\n// functions/lib/seasons.mjs and zone-server/lib/seasons.mjs (generate-version-hash.js fails the build on drift, and\n// tests/seasons.test.mjs holds the three together).\n//\n// NOTHING NEW IS STORED, AND THE WORLD GENERATOR IS UNTOUCHED. A world's terrain is frozen per generator version and its\n// edits are stored against that, so seasons cannot be terrain: they are a pure function of the world's ADDRESS (its\n// tilt and the day of its year its equinox falls on) and of the SHARED CLOCK (system-view.js SIM_EPOCH / SIM_RATE, the\n// clock every body is placed by). Two players on the same world at the same moment are in the same season, and so would\n// a server be, without either of them writing anything down.\n//\n// ── SEASONS ARE DAYLIGHT, NOT WARMTH (the owner, Oct 12: \"change world gen so the entire worlds are one temperature —\n// seasons only affects daylight/geometry\"; \"ONE NUMBER ALWAYS\", \"SEASONS DO NOTHING ELSE\") ─────────────────────────\n// A season used to move the ground too: an even swing of up to ±35 °C a hemisphere (seasonSwingRel, seasonOffsetC,\n// warmthAt), a locked world's lagged rocking sunpoint as its ground felt it (thermalDeclinationDeg), and through those\n// the winter ice (freeze.mjs), the turf's season clock and the spring flowers (growth.mjs) and the season's looks\n// (season-looks.mjs). All of that is gone, with those three modules: a world has ONE temperature everywhere, day and\n// night, all year (temperature.mjs's planet-wide number). What a season still is: its name, how long the sun is up\n// (daylightShare, daylight.mjs), and the sun's path through the sky (axisLean, declinationDeg, lockedDeclinationDeg),\n// polar day and night with it, and so the suit's charging, which follows the sun (sunlight.mjs, energy.mjs).\n//\n// ── THE OWNER'S DECISIONS, AND WHERE EACH LIVES ──────────────────────────────────────────────────────────────────\n//   SEASONS COME FROM AXIAL TILT (tiltOf). Every tilt from 0° to 90° can occur, weighted: about two thirds of worlds\n//     between 10° and 40°, about one in ten beyond 60°, roughly one in six near 0° (spread = weighted). A moon shares its\n//     planet's tilt and year, and so does each of a pair of planets (…4a and …4b): they orbit as one.\n//   A YEAR IS ONE REAL ORBIT, WITH THE ORBITS SLOWED (yearDaysFor). Each planet keeps its place in its system's order,\n//     and that place maps onto 4 to 28 real days on a smooth curve: the innermost planet's year is 4 days (each season a\n//     day), the outermost's 28 (each season a week) (slow = rank). system-view.js assignDynamics turns the year into the\n//     planet's orbital rate, so the planet drawn on its orbit and the season it is in are one fact, and sunlight.mjs\n//     replays the same for the servers' energy rule. Moons keep their quick orbits round their planets.\n//   A HEMISPHERE IS THREE FACES (hemisphereOf, Oct 11): the faces round the north pole corner are the north, those round\n//     the south pole corner the south, each wholly its own season (its name, and its daylight), blended only over the\n//     rounded ground of the six equator edges between them (EDGE_BLEND).\n//   A WORLD LOCKED TO ITS STAR ROCKS (rockDeg): its sunpoint swings north and south by a third of its tilt, at most 30°,\n//     over the year (rock = third), and that rocking is its season's sun.\n//   THE SOLAR DECLINATION (declinationDeg, lockedDeclinationDeg): where the sun stands north or south of a world's\n//     equator today, read by the sky (phase 2) and the daylight, so the two can never be about two different years.\n//   THE SUN'S PATH FOLLOWS THE SEASON (phase 2, \"polar day and night = include\"; axisLean, daylightShare): a turning\n//     world's spin axis leans by its tilt toward a direction fixed among the stars, so its sun climbs higher in summer\n//     and lower in winter, and round the solstices the pole corners have days without a sunset and nights without a\n//     dawn. A locked world's axis nods toward and away from its star by its rock instead.\n//\n// PURE AND DEPENDENCY-FREE, like climate.mjs: no imports, no DOM, no clock of its own (every time is handed in as wall-\n// clock milliseconds on the shared clock), so the twins answer alike and the tests run it in node.\n\nconst TAU = 2 * Math.PI;\nconst DEG = Math.PI / 180;\nconst fin = (v) => typeof v === 'number' && Number.isFinite(v);\nconst clamp = (v, lo, hi) => (v < lo ? lo : v > hi ? hi : v);\nconst frac = (v) => v - Math.floor(v);\n\nexport const SEASONS = {\n  // ── THE YEAR ─────────────────────────────────────────────────────────────────────────────────────────────────────\n  // 4 days for the innermost planet, 28 for the outermost: YEAR_MIN · (YEAR_MAX / YEAR_MIN)^(rank / (n − 1)), so each\n  // step outward lengthens the year by the same factor and the curve has no kinks (a 5-planet system: 4, 6.5, 10.6,\n  // 17.2, 28 days). Days are real days at the speed slider's 1×; the slider divides them as it divides every period.\n  YEAR_MIN_DAYS: 4,\n  YEAR_MAX_DAYS: 28,\n  // A SYSTEM WITH ONE PLANET has no order to place it in, so its distance places it: half its star's habitable-zone\n  // distance or closer is the inner end (4 days), four times it or further the outer end (28), on the same curve by the\n  // logarithm of the distance between. So a lone planet in the habitable zone has a year of about 7.7 days, as the third\n  // of five would.\n  LONE_INNER_HZ: 0.5,\n  LONE_OUTER_HZ: 4,\n  // ── LOCKED WORLDS ROCK ───────────────────────────────────────────────────────────────────────────────────────────\n  // The sunpoint of a world locked to its star swings north and south by a third of its tilt, never more than 30°.\n  ROCK_FRACTION: 1 / 3,\n  ROCK_MAX_DEG: 30,\n  // ── NO SEASONS ───────────────────────────────────────────────────────────────────────────────────────────────────\n  // Below 1° of tilt the sun's path barely moves over the year, and the card says nothing about seasons at all.\n  NONE_BELOW_DEG: 1,\n  // ── THE EQUATOR ──────────────────────────────────────────────────────────────────────────────────────────────────\n  // THE HEMISPHERES ARE FACES (the owner, Oct 11: \"hemisphere should be dependent on northern/southern faces, not split by\n  // the equator\"). The spin axis is the cube's (1, 1, 1) diagonal, so three faces stand round the north pole corner (+x,\n  // +y, +z) and three round the south one (−x, −y, −z); the equator is the zig-zag of the six EDGES where a northern face\n  // meets a southern one, not the plane x + y + z = 0, which cut diagonally across all six faces. Each face is wholly its\n  // own hemisphere's season (hemisphereOf).\n  // …AND A SOFT EDGE BETWEEN THEM (the owner chose to blend): north and south are opposite, and the weight fades from +1\n  // to −1 over the edge's rounded ground, the edge itself half-way (0), over EDGE_BLEND of the world's half-size either\n  // side of it: the fade is EDGE_BLEND · R kubes each way along the ground, about 1.7 kubes on a\n  // 48-kube world, 2.2 on a 64, 3.1 on an 88 (and under one on the smallest, 24-kube worlds). Everywhere else on a face it\n  // is exactly the same.\n  EDGE_BLEND: 0.07,\n};\n// The spin axis of every world, in the voxel grid's frame: climate.mjs SPIN_AXIS, the (1, 1, 1) diagonal the renderer\n// stands up (VOXEL_CORNER_UP turns it to the system's +Y, about which setSpin turns the world). Restated rather than\n// imported so this file has no imports; tests/seasons.test.mjs holds the two equal. NORTH is the side it points to.\nconst S3 = 1 / Math.sqrt(3);\nexport const SPIN_AXIS = Object.freeze([S3, S3, S3]);\nexport const SEASON_NAMES = Object.freeze(['spring', 'summer', 'autumn', 'winter']);\n\n// FNV-1a over a string, finalised: a number in [0, 1) (climate.mjs's hash01, restated for the same reason).\nfunction hash01(s) {\n  let h = 2166136261;\n  for (let i = 0; i < s.length; i++) h = Math.imul(h ^ s.charCodeAt(i), 16777619);\n  h ^= h >>> 15; h = Math.imul(h, 0x2c1b3c6d); h ^= h >>> 12;\n  return (h >>> 0) / 4294967296;\n}\n\n// THE ADDRESS A WORLD'S SEASONS ARE SEEDED BY: its planet's, galaxy.system.star.planet, with a pair's a/b and a moon's\n// own number taken off, so a moon shares its planet's tilt and year and the two of a pair share theirs. null for\n// anything that is not a planet or moon (a star, a malformed address).\nexport function seasonKeyOf(addr) {\n  const p = String(addr == null ? '' : addr).split('.');\n  if (p.length < 4 || !p[3]) return null;\n  const planet = p[3].replace(/[ab]$/, '');\n  if (!/^\\d+$/.test(planet) || !/^\\d+$/.test(p[0]) || !/^\\d+$/.test(p[1]) || !/^\\d+$/.test(p[2])) return null;\n  return `${p[0]}.${p[1]}.${p[2]}.${planet}`;\n}\n\n// ── TILT ─────────────────────────────────────────────────────────────────────────────────────────────────────────\n// THE WEIGHTED SPREAD, as a piecewise-linear inverse of its cumulative share, from a uniform u in [0, 1):\n//   16%  0°-10°, bunched toward 0° (the square: half of them under 2.5°, about one world in twenty under 1°)\n//   66%  10°-40°, evenly: the Earths and Marses\n//    8%  40°-60°\n//   10%  60°-90°: the worlds on their sides\nexport const TILT_BANDS = Object.freeze([\n  { upTo: 0.16, from: 0, to: 10, square: true },\n  { upTo: 0.82, from: 10, to: 40 },\n  { upTo: 0.90, from: 40, to: 60 },\n  { upTo: 1.00, from: 60, to: 90 },\n]);\nexport function tiltFromUnit(u) {\n  const x = fin(u) ? clamp(u, 0, 1) : 0;\n  let lo = 0;\n  for (const b of TILT_BANDS) {\n    if (x < b.upTo || b.upTo === 1) {\n      const t = clamp((x - lo) / (b.upTo - lo), 0, 1);\n      return b.from + (b.to - b.from) * (b.square ? t * t : t);\n    }\n    lo = b.upTo;\n  }\n  return 0;\n}\n// A world's tilt, in degrees (0-90), from its planet's address. 0 for anything without one.\nexport function tiltOf(addr) {\n  const k = seasonKeyOf(addr);\n  return k ? tiltFromUnit(hash01(k + '#tilt')) : 0;\n}\nexport const hasSeasons = (tiltDeg) => fin(tiltDeg) && tiltDeg >= SEASONS.NONE_BELOW_DEG;\n\n// ── THE YEAR ─────────────────────────────────────────────────────────────────────────────────────────────────────\n// A planet's year in real days from its place in its star's order: `rank` 0 for the innermost of `count` planets.\n// `distHz` (its orbital distance over its star's habitable-zone distance) is read only when it is the only one.\nexport function yearDaysFor(rank, count, distHz) {\n  const { YEAR_MIN_DAYS: lo, YEAR_MAX_DAYS: hi, LONE_INNER_HZ: a, LONE_OUTER_HZ: b } = SEASONS;\n  let t;\n  if (fin(count) && count > 1) t = clamp((fin(rank) ? rank : 0) / (count - 1), 0, 1);\n  else t = fin(distHz) && distHz > 0 ? clamp(Math.log(distHz / a) / Math.log(b / a), 0, 1) : 0;\n  return lo * Math.pow(hi / lo, t);\n}\n// …AND ROUND A BLACK HOLE, FASTER (the owner's lensing picks, Oct 28, P3: worlds near the hole \"move visibly faster on their\n// orbits (Kepler's law, from the mass)\"): a period goes as one over the square root of the mass it circles, so the year\n// above is divided by √(the hole's mass in suns), down to HOLE_YEAR_MIN_DAYS. Every other host (no holeMass) is untouched.\nexport const HOLE_YEAR_MIN_DAYS = 1;\nexport function yearDaysRound(host, rank, count, distHz) {\n  const days = yearDaysFor(rank, count, distHz), m = host && host.holeMass;\n  return fin(m) && m > 1 ? Math.max(HOLE_YEAR_MIN_DAYS, days / Math.sqrt(m)) : days;\n}\nexport const DAY_MS = 86400000;\n\n// WHERE IN ITS YEAR A WORLD IS, at wall-clock `ms` on the shared clock, as a phase in [0, 1): 0 the northern spring\n// equinox, 1/4 northern midsummer, 1/2 the autumn equinox, 3/4 midwinter. A year IS an orbit (system-view.js turns the\n// year into the orbital rate), so this advances exactly as the planet goes round its star; where on the orbit its\n// equinox falls is drawn from the address, so neighbouring worlds are not all in step.\nexport function yearPhaseAt(addr, yearDays, ms) {\n  const k = seasonKeyOf(addr);\n  if (!k || !fin(yearDays) || yearDays <= 0 || !fin(ms)) return 0;\n  return frac(hash01(k + '#equinox') + ms / (yearDays * DAY_MS));\n}\n\n// ── THE SUN, NORTH AND SOUTH ─────────────────────────────────────────────────────────────────────────────────────\n// The solar declination, in degrees: how far north of the equator the sun stands today, asin(sin tilt · sin 2πφ):\n// +tilt at northern midsummer, −tilt at midwinter, 0 at the equinoxes.\nexport function declinationDeg(tiltDeg, phase) {\n  if (!fin(tiltDeg) || !fin(phase)) return 0;\n  return Math.asin(clamp(Math.sin(clamp(tiltDeg, 0, 90) * DEG) * Math.sin(TAU * phase), -1, 1)) / DEG;\n}\n// A LOCKED WORLD'S ROCK: how far its sunpoint swings north and south over the year, a third of its tilt, at most 30°.\nexport function rockDeg(tiltDeg) {\n  return fin(tiltDeg) ? Math.min(SEASONS.ROCK_MAX_DEG, Math.max(0, tiltDeg) * SEASONS.ROCK_FRACTION) : 0;\n}\n// …and where its sunpoint stands today, in degrees north (the declination of a locked world's fixed sun).\nexport function lockedDeclinationDeg(tiltDeg, phase) {\n  return fin(phase) ? rockDeg(tiltDeg) * Math.sin(TAU * phase) : 0;\n}\n\n// ── THE SUN'S PATH (World Seasons phase 2) ───────────────────────────────────────────────────────────────────────\n// WHICH WAY A WORLD'S SPIN AXIS LEANS, AND HOW FAR, so that the sky's sun stands at declinationDeg (or a locked world's\n// at lockedDeclinationDeg) with nothing else about the sky moved. The renderer turns every world about the system's +Y\n// (setSpin: rotation.y), and every star and world lies in the Y = 0 plane, so before phase 2 a star seen from any world\n// stood on its equator all year. Now the world's frame is tilted first: the axis leans by `lean` (radians) toward the\n// in-plane bearing `dir` (radians, in the atan2(z, x) sense the orbits are laid out in), and the world spins about that\n// leaning axis exactly as before. So the axis is Y·cos(lean) + (cos dir, 0, sin dir)·sin(lean), and a star on bearing\n// β from the world stands at asin(sin(lean)·cos(β − dir)) north of its equator.\n//   (The planets and the stars still lie in the system's Y = 0 plane. Since the owner's Worlds and Axes decisions (Oct\n//   11) the MOONS do not: each circles in its planet's equator, the plane this lean tips, and the two of a pair in\n//   theirs, so a moon's sun can stand a little above or below where its planet's does; a moon of a planet locked to its\n//   star keeps the system plane. A gas giant leans by this too, for its looks. And the system's plane is itself tilted\n//   against its galaxy's disc, which only the night sky can see. None of it moves a planet's sun, which is all\n//   declinationDeg is about; system-view.js orbitPlaneOf and sunlight.mjs equatorOf say the rest.)\n//   A WORLD THAT TURNS leans by its whole tilt toward a bearing FIXED AMONG THE STARS, as a real planet's axis is: the\n//     planet's orbital angle `orbitAngle` (λ, the bearing of the planet from the centre it goes round, which the\n//     renderer lays out as startAngle − ω·T) and the year's phase φ advance together (a year IS the orbit, B3½), one by\n//     −2π and the other by +2π a year, so dir = 2πφ + λ + π/2 is the same at every moment. The sun is on bearing λ + π,\n//     so its declination is asin(sin(tilt)·cos(λ + π − 2πφ − λ − π/2)) = asin(sin(tilt)·sin 2πφ): declinationDeg,\n//     exactly, for the planet and the two of a pair (both go round their pair's centre on λ). A moon is handed its\n//     planet's λ and year, so its axis is fixed among the stars as its planet's is; the sun it sees is off its planet's\n//     by however far its own orbit carries it round, and its declination by no more than that (the system is drawn\n//     compressed, so round a close giant that is degrees, over the moon's orbit of an hour or two; tests/sun-path.test\n//     .mjs holds it to that bound).\n//     THE SOLAR DAY IS UNCHANGED on average: the world still spins at rotOmega about its axis, and the sun still goes\n//     round once a year, so a day is as long as the card has always listed. Through the year the sun runs a little\n//     ahead of and behind its mean hour (the equation of time, a few minutes of a long day); nothing reads the clock\n//     against it.\n//   A WORLD LOCKED TO ITS STAR keeps its sunpoint on the star's meridian and ROCKS it north and south (rock = third):\n//     its axis leans toward its star (dir = λ + π) by lockedDeclinationDeg, so the sunpoint stands that far north of its\n//     equator, with no drift east or west. A\n//     fixed lean would have made the sunpoint wander in longitude too, by up to 5° at a 30° rock.\n// `out` is filled and returned when given (the renderer asks every frame). null when the world has no seasons to lean\n// by (under 1° of tilt, or no year): its frame is exactly what it was before phase 2.\nexport function axisLean(tiltDeg, locked, phase, orbitAngle, out) {\n  if (!hasSeasons(tiltDeg) || !fin(phase) || !fin(orbitAngle)) return null;\n  const o = out || {};\n  if (locked) { o.lean = lockedDeclinationDeg(tiltDeg, phase) * DEG; o.dir = orbitAngle + Math.PI; }\n  else { o.lean = clamp(tiltDeg, 0, 90) * DEG; o.dir = TAU * phase + orbitAngle + Math.PI / 2; }\n  return o;\n}\n// HOW MUCH OF A DAY THE SUN IS UP, as a share of it (0…1), where the local up stands `sinLat` along the spin axis (the\n// sine of the latitude) and the sun `sinDec` (the sine of its declination): the sunrise equation, cos H = −tan φ·tan δ,\n// with the day lasting 2H of the 2π. 1 is POLAR DAY (the sun never sets), 0 POLAR NIGHT (it never rises): at a pole\n// corner the sun circles the horizon at its declination, so it is one or the other from the equinox to the next; on\n// the equator every day is half a day. The horizon is the geometric one (the sun's centre on it).\nexport function daylightShare(sinLat, sinDec) {\n  if (!fin(sinLat) || !fin(sinDec)) return 0.5;\n  const sl = clamp(sinLat, -1, 1), sd = clamp(sinDec, -1, 1);\n  const across = Math.sqrt(Math.max(0, 1 - sl * sl)) * Math.sqrt(Math.max(0, 1 - sd * sd));   // cos φ · cos δ\n  const along = sl * sd;                                                                      // sin φ · sin δ\n  if (across <= Math.abs(along)) return along > 0 ? 1 : along < 0 ? 0 : 0.5;                 // the pole itself, or past the polar circle\n  return Math.acos(clamp(-along / across, -1, 1)) / Math.PI;\n}\n\n// ── THE HEMISPHERE ───────────────────────────────────────────────────────────────────────────────────────────────\n// Which hemisphere a surface point is in, BY ITS FACE (SEASONS.EDGE_BLEND): a point belongs to the face it stands\n// furthest out along, so it is NORTH when its largest positive coordinate, m+ = max(x, y, z, 0), is at least its largest\n// negative one, m− = max(−x, −y, −z, 0) (the faces round the north pole corner, +x, +y and +z, against those round the\n// south one). `p` is the point in the voxel grid's own frame ({ x, y, z } or [x, y, z]; the walker's position, a cell's\n// centre, or a face's normal). m+ − m− is the point's distance from the nearest equator edge along the ground, in kubes\n// (positive north): on +x near its edge with −y it is x + y, which is 0 on the edge's diagonal and grows a kube a kube.\n// `weight` is how far into its hemisphere it stands: (m+ − m−) / (EDGE_BLEND · max(|x|, |y|, |z|)), held to ±1, so\n// +1 over every northern face, −1 over every southern one, and through 0 on the edge between them; `edge` is that\n// distance itself. `north` is the side the name goes by (the edge itself counts as north).\nexport function hemisphereOf(p) {\n  const x = p ? +(Array.isArray(p) ? p[0] : p.x) : NaN, y = p ? +(Array.isArray(p) ? p[1] : p.y) : NaN, z = p ? +(Array.isArray(p) ? p[2] : p.z) : NaN;\n  if (!fin(x) || !fin(y) || !fin(z)) return { edge: 0, weight: 0, north: true };\n  const mp = Math.max(x, y, z, 0), mn = Math.max(-x, -y, -z, 0), m = Math.max(mp, mn);\n  if (!(m > 0)) return { edge: 0, weight: 0, north: true };\n  const edge = mp - mn;\n  return { edge, weight: clamp(edge / (SEASONS.EDGE_BLEND * m), -1, 1), north: edge >= 0 };\n}\n\n// ── THE SEASON BY NAME ───────────────────────────────────────────────────────────────────────────────────────────\n// The northern season is the quarter of the year the phase is in (spring from the equinox, summer from midsummer…),\n// the astronomical seasons. The south's is two quarters on. `inMs` is how long until the next one starts.\nexport function seasonAt(phase, north, yearDays) {\n  const ph = fin(phase) ? frac(phase) : 0;\n  const q = Math.min(3, Math.floor(ph * 4));\n  const idx = (q + (north === false ? 2 : 0)) % 4;\n  const yearMs = fin(yearDays) && yearDays > 0 ? yearDays * DAY_MS : 0;\n  return { index: idx, name: SEASON_NAMES[idx], next: SEASON_NAMES[(idx + 1) % 4], quarter: q,\n           startPhase: q / 4, endPhase: (q + 1) / 4, inMs: yearMs * ((q + 1) / 4 - ph) };\n}\n\n// ── THE SEASON A CLAIM RECORDS ───────────────────────────────────────────────────────────────────────────────────\n// Something that cannot generate a star system (the zone server; the weekly digest, which reads claims only) cannot work\n// out a world's year (yearDaysFor needs the planet's place in its system), so a claim records it with whether the world\n// is locked to its star (claimWorld, and submitEdits for a claim made before this): { v: 1, yearDays, locked }, or null\n// for a world with no year (a rogue's). It is a calendar, not a climate: it names the season and nothing else. (It was\n// freeze.mjs's, for the winter ice, until the owner's one-temperature rule of Oct 12 took the ice away.)\nexport function seasonRecordOf(yearDays, locked) {\n  return fin(yearDays) && yearDays > 0 ? { v: 1, yearDays, locked: !!locked } : null;\n}\n// A record, read tolerantly: { yearDays, locked } for a good one, null for \"no year\", undefined for none at all (so a\n// caller can tell \"known: none\" from \"not known yet\", as with the sea).\nexport function readSeasonRecord(rec) {\n  if (rec === undefined) return undefined;\n  if (!rec || typeof rec !== 'object' || rec.v !== 1 || !fin(rec.yearDays) || !(rec.yearDays > 0)) return null;\n  return { yearDays: rec.yearDays, locked: !!rec.locked };\n}\n\n// ── EVERYTHING AT ONCE, for one world at one moment ──────────────────────────────────────────────────────────────\n// `world` is { addr, yearDays, ms, p, locked }: its address, its year (the renderer's, from its orbit), the shared\n// clock's time, the surface point (voxel frame) the hemisphere is read at, and whether it is locked to its star.\n// Returns null for a world with no year (a rogue's, or a star).\n//   tilt, seasonal       its tilt, and whether it has seasons to speak of (hasSeasons)\n//   yearDays, phase      its year, and where in it the world is\n//   declinationDeg       the sun's declination today (phase 2's sun path; a locked world's rocking sunpoint)\n//   north, weight        the hemisphere at `p`, and how far into it `p` stands\n//   season               seasonAt: the season there by name, the next one, and when it comes\n//   rockDeg              a locked world's rock (0 on a turning one)\n// Nothing here is a temperature (the owner, Oct 12: seasons are daylight and geometry only).\nexport function worldSeason(world) {\n  const w = world || {};\n  if (!seasonKeyOf(w.addr) || !fin(w.yearDays) || w.yearDays <= 0) return null;\n  const tilt = tiltOf(w.addr), phase = yearPhaseAt(w.addr, w.yearDays, w.ms), locked = !!w.locked;\n  const hemi = hemisphereOf(w.p), season = seasonAt(phase, hemi.north, w.yearDays);\n  return {\n    tilt, seasonal: hasSeasons(tilt), locked, yearDays: w.yearDays, phase,\n    declinationDeg: locked ? lockedDeclinationDeg(tilt, phase) : declinationDeg(tilt, phase),\n    north: hemi.north, weight: hemi.weight, season, rockDeg: locked ? rockDeg(tilt) : 0,\n  };\n}\n"],"mappings":"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","names":[],"file":"seasons.mjs"}