Add core engine: tarot + astrology reading generator with CLI and i18n

Plain-C, dependency-free engine that produces a personalized daily
Celtic Cross tarot spread and natal-chart/transit astrology reading,
plus a standalone CLI (dist/deck-engine) and run.sh wrapper. Includes
English/German output via a simple key=value translation format, and
docs/diagrams describing the architecture and I/O.
This commit is contained in:
ml
2026-07-04 06:49:18 +02:00
commit a93e5d4cdc
60 changed files with 17134 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
# Architecture
See [`CLAUDE.md`](../CLAUDE.md) for build commands and the sharp edges to
know before changing the engine. This document is the visual overview.
## Components
![Component architecture](images/architecture.png)
- **`res/`** holds the source assets: the 22 Rider-Waite-Smith Major
Arcana card images and A. E. Waite's *Pictorial Key to the Tarot* PDF
(public domain), which `tarot_data.c`'s card and position text is
condensed from.
- **`engine/third_party/astronomy/`** is a vendored, unmodified copy of
[cosinekitty/astronomy](https://github.com/cosinekitty/astronomy)
(MIT), pinned to a specific commit — see its `VENDORED.md`.
- **`engine/src/`** is the core engine. Everything except `main.c`,
`reading.c`'s `reading_print_text`/`reading_print_html` functions, and
`i18n.c`'s file-loading half is free of `stdio`/CLI assumptions,
specifically so it can be linked into the Pebble watchapp later without
rework.
- `rng.c` — deterministic string-seeded PRNG (FNV-1a + splitmix64).
- `tarot.c` / `tarot_data.c` — the Celtic Cross draw and its content.
- `astro.c` — natal chart + daily transits, built on the vendored
Astronomy Engine.
- `i18n.c` — loads a `key=value` translation file into a small
fixed-size catalog; every display-text lookup elsewhere falls back to
the built-in English string if no catalog is loaded or a key is
missing from it.
- `reading.c` — orchestrates the above into one `DailyReading` and
(desktop-only) renders it as text or HTML.
- `main.c` — CLI argument parsing, the only consumer of `reading_print_*`.
- **`engine/i18n/`** holds the translation source files (`en.lang`,
`de.lang`, ...), one `key=value` file per language — see
[`CLAUDE.md`](../CLAUDE.md) for the key-naming convention.
- **`dist/`** is generated by `make`: the `deck-engine` binary, a copy of
the card art under `img/`, a copy of the translation files under
`i18n/`, `run.sh`, and a `user.properties` template that's seeded once
and never overwritten by later builds.
- **The Pebble watchapp does not exist yet.** When it's built, it will
call `reading_generate()` directly and walk the returned struct to lay
out its own screens — it has no reason to touch `reading_print_*`.
## Dataflow
![Dataflow for one reading](images/dataflow.png)
A single reading is produced by one call: `reading_generate(seed, birth,
utc_moment, &out)` in `reading.c`. It fans out to three independent
computations and combines their results:
1. **`astro_compute_natal_chart(birth)`** → `NatalChart` — geocentric
ecliptic sign/degree for the Sun through Pluto, the Ascendant, and
whole-sign houses, all from the birth date/time/location.
2. **`astro_compute_daily_transits(utc_moment, natal_chart)`** →
`DailyTransits` — today's planetary positions, the Moon phase, and
every transiting-to-natal aspect within orb. Depends on the natal
chart (read-only) to compute aspects against it.
3. **`tarot_draw_celtic_cross(seed)`** → `CelticCrossSpread` — ten cards
with position and upright/reversed orientation, fully determined by
the seed string alone.
These three results are combined into one `DailyReading` struct, which is
either rendered by `reading_print_text`/`reading_print_html` (used by the
CLI) or, in the future, walked directly by watchapp UI code. See
[`input-output-format.md`](input-output-format.md) for the exact fields
and output formats.
Every display string produced along the way (`tarot_data.c`'s card and
position text, `astro.c`'s body/sign/moon-phase/aspect names, and
`reading.c`'s section headers) is looked up in `i18n.c`'s translation
catalog, with the built-in English text as the fallback. `main.c` loads
the catalog from `--lang`/`--i18n-dir` (or `run.sh`'s `lang=` property)
before calling `reading_generate`, so this is orthogonal to the three
computations above — it only affects how their results are rendered as
text, not the underlying `TarotCard`/`ZodiacSign`/etc. enum values.
Both entry points (direct CLI flags, or `run.sh` pulling the seed/date
from the OS clock and birth data from `user.properties`) converge on the
same `main.c` argument parsing before calling `reading_generate` — there
is exactly one code path from parsed input to a reading.
## Regenerating the diagrams
The diagrams are Graphviz sources in `docs/diagrams/`, rendered to PNG:
```bash
cd docs
dot -Tpng -Gdpi=150 diagrams/architecture.dot -o images/architecture.png
dot -Tpng -Gdpi=150 diagrams/dataflow.dot -o images/dataflow.png
```