{"_id":"@amplib/cosmos","name":"@amplib/cosmos","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@amplib/cosmos","description":"Calculations about the relationship between the earth, moon, sun, planets, and more.","version":"0.1.0","main":"dist/index.js","types":"dist/index.d.ts","publishConfig":{"access":"public"},"homepage":"https://amplib.app/cosmos","repository":{"type":"git","url":"git+https://github.com/another-machine/public-library.git","directory":"packages/amplib-cosmos"},"tsup":{"entry":["src/index.ts"],"clean":true,"dts":true,"format":"esm","minify":false,"sourcemap":true,"splitting":false,"noExternal":["astronomy-engine"]},"scripts":{"build":"tsup","watch":"tsup --watch","test":"tsx test/run.ts && ../../scripts/tz-determinism.sh","test:accuracy":"tsx test/run.ts","test:determinism":"../../scripts/tz-determinism.sh","prepack":"npm run build"},"author":{"name":"Another Machine"},"license":"Apache-2.0","type":"module","dependencies":{"astronomy-engine":"^2.1.19"},"devDependencies":{"tsx":"^4.19.2"},"_id":"@amplib/cosmos@0.1.0","gitHead":"36afb74ac982cbdf7db71bb8c6ede0305b44ba35","bugs":{"url":"https://github.com/another-machine/public-library/issues"},"_nodeVersion":"20.11.0","_npmVersion":"10.2.4","dist":{"integrity":"sha512-wrbcHacKSKSgfYq0LqMcwBq1zT8riI3wg7amAdNi2ILbAgpTDXTPq80WDa8cPPtQ0PSyGk+C3GoaDij0O89+Sw==","shasum":"5a50d16e886ce6c8f1cae2552671012d5ec36696","tarball":"https://registry.npmjs.org/@amplib/cosmos/-/cosmos-0.1.0.tgz","fileCount":6,"unpackedSize":1009637,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDrP3dQNBhKnGWnN1NsnVrb6+TQZTC5UWxBOvIaaQXz+gIgPnJ77oVP8+P0BzD0nopoDhG0yhRuEN3muHjmgkZ7o0c="}]},"_npmUser":{"name":"jakealbaugh","email":"jake.albaugh@gmail.com"},"directories":{},"maintainers":[{"name":"jakealbaugh","email":"jake.albaugh@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cosmos_0.1.0_1785544076226_0.29208649424976985"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-01T00:27:56.075Z","0.1.0":"2026-08-01T00:27:56.372Z","modified":"2026-08-01T00:27:56.591Z"},"maintainers":[{"name":"jakealbaugh","email":"jake.albaugh@gmail.com"}],"description":"Calculations about the relationship between the earth, moon, sun, planets, and more.","homepage":"https://amplib.app/cosmos","repository":{"type":"git","url":"git+https://github.com/another-machine/public-library.git","directory":"packages/amplib-cosmos"},"author":{"name":"Another Machine"},"bugs":{"url":"https://github.com/another-machine/public-library/issues"},"license":"Apache-2.0","readme":"# @amplib/cosmos\n\nThe state of the sky for a given time and place, normalised for driving sound.\n\n```ts\nimport { generate } from \"@amplib/cosmos\";\n\nconst cosmos = generate({ latitude: 40.7128, longitude: -74.006 });\n\ncosmos.moon.phase.unitRange; // 0.56 — position in the synodic cycle\ncosmos.moon.phase.sin; // continuous across the new-moon wrap\ncosmos.sun.daylightProgress.unitRange; // 0.24 — sunrise to sunset\ncosmos.tides.potential.unitRange; // 0.72 — two peaks per lunar day\ncosmos.seed.code; // \"005FJZ96\" — feed to @amplib/procedural-generation\n```\n\n[Live demo](https://another-machine.github.io/public-library/cosmos/)\n\n## Design\n\n**It is a pure function.** Given the same latitude, longitude, and UTC\ntimestamp, `generate()` returns the same result on every machine, in every\ntimezone, in any JavaScript runtime. Nothing in the library reads the host\nclock, the host timezone, or the host locale. This matters because the sibling\npackage exists for \"shared procedural experiences between disconnected\ndevices\" — two phones have to agree without talking to each other, and that is\nonly possible if the input fully determines the output.\n\n`npm test` checks this by running an 840-sample sweep under nine timezones,\nincluding half-hour and 45-minute offsets, and comparing hashes.\n\n**Positions come from an ephemeris.** `astronomy-engine` (MIT, no\ndependencies) is accurate to roughly an arcminute for the Sun, Moon, and\nplanets over 1700–2200. It is reached through exactly one file,\n[`src/ephemeris.ts`](src/ephemeris.ts), so the backend can be swapped without\ntouching anything else.\n\n**Interpretation is ours.** The sidereal-time and rotation-angle derivations,\nthe tidal model, the normalisation contract, the timescale grouping, and the\nseed derivation all live in this package. That is where the value is — an\nephemeris says where Jupiter is, but nothing about how a number should behave\nwhen it feeds an oscillator.\n\n## The value contract\n\nEvery numeric leaf carries its real-world value alongside pre-normalised forms:\n\n```ts\n{\n  value: 0.5077,      // real value, never clamped\n  unit: \"degrees\",\n  min: 0.4885,        // the domain used to normalise\n  max: 0.5683,\n  unitRange: 0.24,    // always in [0, 1]\n  bipolarRange: -0.52 // always in [-1, 1], always 2 * unitRange - 1\n}\n```\n\nThree guarantees hold everywhere, and the test suite sweeps eight locations\nacross six years asserting them:\n\n1. `unitRange` is in `[0, 1]`.\n2. `bipolarRange` is in `[-1, 1]` and equals `2 * unitRange - 1`.\n3. `min`/`max` state the domain, so you can re-derive or re-scale.\n\nThere is a fourth property the tests check that is a design goal rather than a\nhard guarantee: **no value is pinned to a sliver of its range.** A field\nnormalised against the wrong domain is technically in contract and useless in\npractice. Each planet's distance, brightness, and apparent size are therefore\nnormalised against that planet's own extremes rather than a solar-system-wide\nscale — otherwise Venus's near-circular orbit would occupy 0.03% of the range\nand read as a constant.\n\n### Cyclic values\n\nAnything that wraps — an angle, a phase, a time of day — is a `CyclicValue`,\nwhich adds `sin`, `cos`, and `period`:\n\n```ts\ncosmos.moon.phase.unitRange; // jumps 1 → 0 at new moon\ncosmos.moon.phase.sin; // continuous through it\ncosmos.moon.phase.cos;\n```\n\nUse `unitRange` when you want the hard reset — triggering an event, indexing a\ntable. Use `sin`/`cos` for anything continuous, because feeding the wrap point\ninto a filter cutoff produces an audible click.\n\n### Events\n\nRise and set times are `EventValue`:\n\n```ts\n{ timestamp: 1785535912153, iso: \"2026-07-31T09:51:52.153Z\", secondsUntil: -12420 }\n```\n\n`null` is a real answer, not an error: inside the polar circles the Sun stays\nup or down for weeks, and the Moon skips a rise roughly once a month because\nits day is 24h50m. Branch on it rather than substituting zero. `sun.dayLength`\nreports 24 or 0 in those cases so you always have a usable number.\n\n## Timescales\n\nThe result tree is organised by body, but the useful question when mapping the\nsky onto music is how fast something moves. `cosmos.timescales` regroups the\nsame value objects — by reference, nothing is copied — into four bands:\n\n| Band         | Period        | Suits                                  |\n| ------------ | ------------- | -------------------------------------- |\n| `rotational` | hours         | rhythm, filter sweeps, stereo movement |\n| `lunar`      | days to weeks | phrase length, register, density       |\n| `annual`     | months        | key centre, mode, timbre               |\n| `epochal`    | years         | long-form structure, tuning drift      |\n\n```ts\nfor (const signal of cosmos.timescales.rotational.signals) {\n  signal.path; // \"sun.hourAngle\"\n  signal.periodSeconds; // 86400\n  signal.cyclic; // true — carries sin/cos\n  signal.value.unitRange;\n}\n```\n\n## Tides\n\n`cosmos.tides` is the equilibrium tide-generating potential — the real\nsecond-degree term, `(3cos²θ − 1) / 2` scaled by `GM/d³`, summed over the Moon\nand the Sun.\n\nBecause it goes as `cos²`, the Moon overhead and the Moon underfoot both raise\na bulge: two high tides a lunar day, not one. And because the solar term adds\nto the lunar one only when they share an axis, spring and neap tides fall out\nwithout being modelled — `tides.range` traces the synodic month on its own.\nMusically that is the useful part: a semidiurnal pulse whose depth breathes\nover four weeks.\n\nThese are equilibrium tides on a hypothetical ocean over a rigid Earth. Real\ncoastal tides are dominated by basin resonance and can lag this by hours. It is\nan honest astronomical driving force, not a tide table.\n\n## Seeds\n\n`cosmos.seed` is derived from **quantised inputs**, not from the computed sky:\n\n```ts\ngenerate({\n  latitude,\n  longitude,\n  timestamp,\n  seedResolution: { seconds: 3600, degrees: 0.25 },\n});\n```\n\nTwo devices in the same position cell and time bucket agree with no\ncoordination. The quantisation absorbs GPS jitter and clock drift.\n\nDeriving the seed from the cosmic state instead is tempting and wrong.\nECMAScript specifies `Math.sin`, `Math.cos`, and `Math.pow` as\nimplementation-approximated — V8, JavaScriptCore, and SpiderMonkey each return\nresults differing in the last bits. Every value here passes through dozens of\nthose calls. Two engines would usually land in the same quantised bucket and\nagree, but near a boundary they would not, and the failure would be rare,\nsilent, and unreproducible. Latitude, longitude, and a timestamp are exact\ndoubles, and the hash is FNV-1a over `Math.imul`, which is exact everywhere.\n\nThe state still shapes the music. It just does not shape the seed.\n\n## Descriptions\n\n`generate()` allocates no strings, which is what makes calling it every frame\nreasonable. Text is opt-in:\n\n```ts\nimport { describe, describeLines } from \"@amplib/cosmos\";\n\ndescribe(cosmos)[\"moon.phase\"]; // \"0.5616 (0.562)\"\nconsole.log(describeLines(cosmos)); // aligned and sorted, for a <pre> or a log\n```\n\n## Performance\n\nA full warm evaluation is about 0.24 ms; sun and moon only (`skipPlanets:\ntrue`) is about 0.07 ms. Both fit comfortably inside a 16 ms frame.\n\nRise/set and moon-quarter searches are iterative and cost roughly a millisecond\neach, so they are memoised per local solar day and per observer position\nrounded to 0.01°. A running clock hits the same cache entry all day. Continuous\nquantities are recomputed every call.\n\n## Accuracy\n\nVerified against external references in [`test/run.ts`](test/run.ts) — defining\nconstants, published rise/set times, and eclipse instants, which are\nunambiguous syzygies:\n\n| Check                              | Result                           |\n| ---------------------------------- | -------------------------------- |\n| GMST at J2000.0                    | matches 18h41m50.548s to 1e-6 h  |\n| Moon phase at six eclipse instants | within 0.5° of elongation        |\n| Sunrise/sunset, three cities       | within 2 minutes of published    |\n| Equation of time extremes          | within 1 minute                  |\n| Inner-planet elongation limits     | never exceeded over 1500 samples |\n| Planet magnitudes                  | inside published ranges          |\n\n```bash\nnpm test              # accuracy suite + timezone determinism\nnpm run test:accuracy # accuracy only\n```\n","readmeFilename":"README.md","_rev":"1-1f09dcd2d6ccc9ca428ca8f54fb371de"}