Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Pages

# Builds the landing site with its live demo (scripts/build-site.sh: site/, the WASM package
# and a 1950-2050 DE440s excerpt) and deploys it to GitHub Pages. The repository's Pages
# source must be set to "GitHub Actions".

on:
push:
branches: [main]
paths:
- "site/**"
- "crates/astroceleste-engine/**"
- "crates/astroceleste-engine-wasm/**"
- "scripts/build-site.sh"
- ".github/workflows/pages.yml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: pages
cancel-in-progress: false

env:
CARGO_TERM_COLOR: always

jobs:
build:
if: github.repository_owner == 'ffalcinelli'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "24"
- uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master
with:
toolchain: stable
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
key: pages
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: kernels
key: jpl-kernels-de440s
- run: npm install --global wasm-pack@0.15.0
- run: scripts/build-site.sh
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: target/site

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
permissions:
pages: write
id-token: write
steps:
- id: deployment
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
10 changes: 10 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ pytest crates/astroceleste-engine-py/tests
# WASM bindings
wasm-pack build --target nodejs --out-dir pkg-node crates/astroceleste-engine-wasm
node crates/astroceleste-engine-wasm/tests/golden.mjs

# Landing site + live demo (needs wasm-pack): builds target/site, deployed by pages.yml
scripts/build-site.sh && python3 -m http.server -d target/site
```

The golden tests (`golden_chart`, `golden_horary`, `golden_derived`) **silently skip** (print
Expand Down Expand Up @@ -63,6 +66,13 @@ Core pipeline (`crates/astroceleste-engine/src`):
- `catalog/` — static tables (fixed stars, lunar mansions, derived-house meanings), generated
code excluded from rustfmt/clippy.

Docs outside rustdoc: `docs/` (API guide, ephemerides, accuracy, architecture), `CONTRIBUTING.md`,
`SECURITY.md`, `RELEASING.md`. `site/` is the static landing page with a WASM demo (`demo.js`
imports `pkg/astroceleste_engine_wasm.js` and loads a 1950–2050 excerpt; the range is set in
`scripts/build-site.sh`). When options, ayanamsas, entry points or the result shape change,
update `docs/api.md` and the demo form too. README is also the crates.io readme: link repo files
with absolute GitHub URLs.

The public API is curated: every module is private except `ephemeris`, and `lib.rs` re-exports
the entry points, `ChartRequest`, `EngineError`, `UtcInstant` and every type reachable from a
result (`Chart`, `Placement`, `HoraryData`, …). A new result type must be added to those
Expand Down
133 changes: 116 additions & 17 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,129 @@
# Contributing

## Correctness first
Thanks for your interest in astroceleste-engine! Bug reports, fixes, documentation and
ports of missing features are all welcome.

## Reporting bugs

Every number the engine produces is checked against a reference:
- **A chart value looks wrong?** Open an issue with the complete request (UTC moment,
latitude, longitude, house system, zodiac type, ayanamsa, orb settings if any), the value
the engine returns, the value you expected, and where the expected value comes from
(another program, an ephemeris, a published table).
- **A crash, or an error you did not expect?** Include the version, the binding (Rust,
Python, WebAssembly), the platform and a minimal reproducer.
- **A security issue?** Do not open an issue. See [SECURITY.md](SECURITY.md).

- `tests/fixtures/spk_reference.json`: raw kernel states from `jplephem`
(`scripts/make_spk_fixtures.py`).
- `tests/fixtures/{natal,horary,transits,synastry,derived,errors}.json`: complete charts from
the reference implementation. A change that moves any of them must explain why the new
value is more correct, and regenerate the fixtures in the same pull request.
## Setup

Run the full suite with the complete kernel before opening a pull request:
- Rust ≥ 1.85 (the MSRV, also tested in CI) via [rustup](https://rustup.rs).
- The full JPL kernel: `scripts/fetch-kernels.sh` downloads `kernels/de440s.bsp` (~32 MB,
gitignored). Without it the tests use a committed one-year excerpt, and **the golden
chart tests silently skip** (they print "skipping" and pass). A green run without the
kernel does not validate charts.
- For the Python bindings: Python ≥ 3.12, a virtualenv, `pip install maturin pytest`.
- For the WebAssembly bindings: `rustup target add wasm32-unknown-unknown`,
[wasm-pack](https://rustwasm.github.io/wasm-pack/) and Node.js.

## Everyday commands

```bash
scripts/fetch-kernels.sh
cargo fmt --all && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace
cargo test --workspace --exclude astroceleste-engine-py # fast: committed excerpt
cargo test -p astroceleste-engine --release # with the kernel: golden + full range
cargo fmt --all && cargo clippy --workspace --all-targets -- -D warnings
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps

# Targets the apps depend on must keep compiling
cargo build -p astroceleste-engine -p astroceleste-engine-wasm --lib --target wasm32-unknown-unknown
cargo check -p astroceleste-engine --lib --target aarch64-linux-android # and aarch64-apple-ios

# Python bindings (in a virtualenv)
maturin develop -m crates/astroceleste-engine-py/Cargo.toml
pytest crates/astroceleste-engine-py/tests

# WebAssembly bindings
wasm-pack build --target nodejs --out-dir pkg-node crates/astroceleste-engine-wasm
node crates/astroceleste-engine-wasm/tests/golden.mjs

# Landing site with the live demo, served at http://localhost:8000
scripts/build-site.sh && python3 -m http.server -d target/site
```

## Dependencies
The Python crate is excluded from `cargo test` because it links against libpython. Test it
through maturin.

[docs/architecture.md](docs/architecture.md) explains how the code is organised.

## Correctness first

The engine is a port of the Astroceleste reference implementation (Python on Skyfield), and
every number it produces is checked against a reference:

- `tests/fixtures/{natal,horary,transits,synastry,derived,errors}.json`: complete charts
from the reference implementation. These fixtures are the acceptance spec. The engine
must reproduce them with floats within 1e-9, the same key order and the same int vs
float number types.
- `tests/fixtures/spk_reference.json` and the `tests/data/de440s_2000.bsp` excerpt: raw
kernel states from `jplephem` (`scripts/make_spk_fixtures.py`).
- `tests/fixtures/reduction.json`: stage-by-stage reduction values from Skyfield
(`scripts/make_reduction_fixtures.py`).

A change that moves any fixture value must explain why the new value is more correct, and
regenerate the fixtures in the same pull request.

When porting reference code:

- Use `pyfloat.rs` (`rem`, `floordiv`, `divmod`, `round`, `round_int`) wherever the
reference uses Python's `%`, `//` or `round()`. Rust's `%` and `f64::round` differ at
the boundaries, and the difference moves signs and orbs.
- Serialize integers and floats exactly as the reference does.
- Keep the module doc comment naming the reference function it ports (for example
`SkyfieldEngine.calculate_planets`).
- The embedded tables (`src/data/*.bin`, `src/frames/nutation_data.rs`) are generated by
`scripts/gen_tables.py`. Do not edit them by hand.

See [docs/accuracy.md](docs/accuracy.md) for the full picture.

## Public API

- The API is curated in `lib.rs`: modules are private, and entry points and every type
reachable from a result are re-exported. A new result type must be added there.
- `missing_docs` is enabled and CI denies warnings: every public item needs a doc comment.
- Public enums that may grow (`EngineError`, `SpkError`, `HouseSystem`) are
`#[non_exhaustive]`.
- A change to the bindings updates the Python type stub (`astroceleste_engine.pyi`), the
binding READMEs and both binding test suites.

## Dependencies and portability

The engine ships inside closed-source applications and in the browser, so it stays
dependency-light and permissively licensed (`deny.toml`). It must keep building for
`wasm32-unknown-unknown`, Android and iOS: no C code, no platform-specific I/O in the core.
dependency-light (the core depends only on `serde` and `serde_json`) and permissively
licensed (`deny.toml`, checked by cargo-deny in CI). It must keep building for
`wasm32-unknown-unknown`, Android and iOS: no C code and no platform-specific I/O in the
core.

## Commits and pull requests

Use [Conventional Commits](https://www.conventionalcommits.org). `release-plz update`
derives the version bump and the changelog from them:

```text
feat: add Koch houses
fix(houses): Placidus cusps above the polar circle
docs: explain kernel excerpts
feat!: rename ChartRequest::zodiac_type # breaking change
```

Keep each pull request to one logical change. Before opening it, check that:

- [ ] `cargo fmt`, `clippy -D warnings` and `cargo doc` are clean;
- [ ] the tests pass **with the full kernel** (`scripts/fetch-kernels.sh`);
- [ ] binding changes are tested through maturin and wasm-pack;
- [ ] any fixture change is explained and regenerated in the same pull request;
- [ ] docs (rustdoc, READMEs, `docs/`) match the change.

Maintainers release by pushing a version tag: see [RELEASING.md](RELEASING.md).

## Commits
## License

Use [Conventional Commits](https://www.conventionalcommits.org): `release-plz update` derives
the version bump and the changelog from them. By contributing you agree to license your work
under MIT OR Apache-2.0.
By contributing you agree to license your work under MIT OR Apache-2.0, without any
additional terms or conditions.
85 changes: 29 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ It is the calculation core of [Astroceleste](https://astroceleste.it). The same
on the server (Python bindings), in the desktop and mobile apps (native) and in the browser
(WASM), so every platform computes the same chart down to the arcsecond.

**[Website and live demo](https://ffalcinelli.github.io/astroceleste-engine/)** · [API docs](https://docs.rs/astroceleste-engine) ·
[Guides](https://github.com/ffalcinelli/astroceleste-engine/tree/main/docs)

> **Experimental (0.0.x):** the API may change in any release. Pin an exact version.

> Status: the complete chart pipeline is ported and matches the reference implementation
Expand Down Expand Up @@ -43,13 +46,23 @@ Charts serialize to the same JSON as the Astroceleste API. Other entry points:

- **Python** (`pip install astroceleste-engine`, CPython ≥ 3.12): `Engine([kernel paths])`
with `.chart()`, `.horary()`, `.transit()`, plus `synastry()` and `derived_chart()`,
returning plain dicts. See [crates/astroceleste-engine-py](crates/astroceleste-engine-py).
returning plain dicts. See [crates/astroceleste-engine-py](https://github.com/ffalcinelli/astroceleste-engine/tree/main/crates/astroceleste-engine-py).
- **JavaScript / WebAssembly** (`npm install astroceleste-engine`): the same API over
kernels loaded from memory, for browsers and Node. See
[crates/astroceleste-engine-wasm](crates/astroceleste-engine-wasm).
[crates/astroceleste-engine-wasm](https://github.com/ffalcinelli/astroceleste-engine/tree/main/crates/astroceleste-engine-wasm).

Both are tested against the same golden fixtures as the Rust crate.

## Documentation

- [API reference on docs.rs](https://docs.rs/astroceleste-engine)
- [API guide](https://github.com/ffalcinelli/astroceleste-engine/blob/main/docs/api.md): request options, the chart JSON, error codes in every
language
- [Ephemerides](https://github.com/ffalcinelli/astroceleste-engine/blob/main/docs/ephemerides.md): kernels, date coverage, loading from memory,
smaller excerpts for the web and mobile
- [Accuracy](https://github.com/ffalcinelli/astroceleste-engine/blob/main/docs/accuracy.md): how every number is verified
- [Architecture](https://github.com/ffalcinelli/astroceleste-engine/blob/main/docs/architecture.md): crates and calculation pipeline

## Layout

| Path | Content |
Expand All @@ -63,75 +76,35 @@ Both are tested against the same golden fixtures as the Rust crate.
| `scripts/make_spk_fixtures.py` | rebuilds the excerpt and the SPK reference states with `jplephem` |
| `scripts/make_reduction_fixtures.py` | stage-by-stage reference values from Skyfield |
| `scripts/gen_tables.py` | regenerates the embedded ΔT, nutation and Chiron tables |
| `scripts/build-site.sh` | builds the landing site and live demo into `target/site/` |
| `docs/` | guides |
| `site/` | landing site and live demo, deployed to GitHub Pages |

## Contributing

## Development
Contributions are welcome: see [CONTRIBUTING.md](https://github.com/ffalcinelli/astroceleste-engine/blob/main/CONTRIBUTING.md) for setup, the test
suite and the rules that keep every platform identical. Report vulnerabilities privately,
as described in [SECURITY.md](https://github.com/ffalcinelli/astroceleste-engine/blob/main/SECURITY.md). Maintainers release by pushing a version
tag ([RELEASING.md](https://github.com/ffalcinelli/astroceleste-engine/blob/main/RELEASING.md)).

```bash
cargo test --workspace --exclude astroceleste-engine-py # uses the committed excerpt
scripts/fetch-kernels.sh # optional: full de440s (1849-2150), enables the full-range tests
scripts/fetch-kernels.sh # full de440s (1849-2150): enables the golden and full-range tests
cargo clippy --workspace --all-targets -- -D warnings

# Python bindings (in a virtualenv)
pip install maturin pytest && maturin develop -m crates/astroceleste-engine-py/Cargo.toml
pytest crates/astroceleste-engine-py/tests

# WebAssembly bindings (needs wasm-pack and the wasm32-unknown-unknown target)
wasm-pack build --target nodejs --out-dir pkg-node crates/astroceleste-engine-wasm
node crates/astroceleste-engine-wasm/tests/golden.mjs
```

## Releasing

Releases are trunk-based: there are no release branches or release PRs. Pushing a
`vX.Y.Z` tag on `main` publishes that version.

1. Commits on `main` follow [Conventional Commits](https://www.conventionalcommits.org)
(`feat:`, `fix:`, `perf:`, `refactor:`, … and `!` for breaking changes). While the
version is 0.0.x, `feat`/`fix` bump the patch version and a breaking change bumps the
minor version.
2. Prepare the release on `main`: run `release-plz update` (or edit by hand) to bump the
workspace version, the bindings' `astroceleste-engine` dependency version and
`CHANGELOG.md`, then commit as `chore: release vX.Y.Z` and push.
3. Once CI is green, tag that commit and push the tag:
`git tag vX.Y.Z && git push origin vX.Y.Z`.
4. The `Release` workflow checks that the tag is on `main`, matches the version in
`Cargo.toml` and has a `CHANGELOG.md` entry. It then publishes the crate to crates.io,
creates the GitHub Release from the changelog entry, and publishes the Python wheels
(Linux x86_64/aarch64 glibc and musl, macOS universal2, Windows x64, sdist) to PyPI
and the WebAssembly package to npm.

Protect `v*` tags with a tag ruleset so only maintainers can trigger a release.

All three registries use Trusted Publishing (OIDC), so no long-lived token is stored.
crates.io and npm can only trust a workflow for a package that already exists, so the
first release uses short-lived tokens instead:

1. Create the `release`, `pypi` and `npm` environments in the repository settings
(optionally with required reviewers).
2. **crates.io**: create an API token scoped to `publish-new` and `publish-update` for the
crate `astroceleste-engine`, with a short expiry, and store it as the
`CARGO_REGISTRY_TOKEN` secret of the `release` environment.
3. **npm**: create a granular access token with publish rights and a short expiry, and store
it as the `NPM_TOKEN` secret of the `npm` environment.
4. **PyPI**: add a *pending* trusted publisher for project `astroceleste-engine` (workflow
`release.yml`, environment `pypi`). No token is needed.
5. Push the `vX.Y.Z` tag for the version in `Cargo.toml`.
6. Once the packages exist, add the trusted publisher on crates.io (crate settings →
*Trusted Publishing*: this repository, workflow `release.yml`, environment `release`)
and on npm (package settings: workflow `release.yml`, environment `npm`). Then delete
both secrets and revoke the tokens. The workflow prefers OIDC whenever it is available.

## Ephemerides

The engine reads NASA JPL SPK kernels (DE440s by default, DE441 for dates outside 1849–2150).
Dates outside the loaded kernels are reported as errors, never approximated.
Dates outside the loaded kernels are reported as errors, never approximated. See
[docs/ephemerides.md](https://github.com/ffalcinelli/astroceleste-engine/blob/main/docs/ephemerides.md) for coverage, sizes and excerpts.

## License

Licensed under either of

- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
- MIT license ([LICENSE-MIT](LICENSE-MIT))
- Apache License, Version 2.0 ([LICENSE-APACHE](https://github.com/ffalcinelli/astroceleste-engine/blob/main/LICENSE-APACHE))
- MIT license ([LICENSE-MIT](https://github.com/ffalcinelli/astroceleste-engine/blob/main/LICENSE-MIT))

at your option.

Expand Down
Loading
Loading