# 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 the single root `Makefile`: the `deck-engine` and `interpreter-cli` binaries, a copy of the card art under `img/`, a copy of the translation files under `i18n/`, `run-engine.sh`/`run-interpreter.sh`, and `user.properties`. Since `dist/` itself is disposable, `user.properties` also gets backed up to `engine/scripts/user.properties.local` (gitignored) once it's filled in, and restored from there if `dist/` is ever wiped — see `CLAUDE.md`'s "Build & run". - **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-engine.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-engine.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. `run-interpreter.sh` reuses those same OS-clock/`user.properties` inputs, but calls `deck-engine --format json` and pipes the result into `interpreter-cli` instead of printing a reading directly. ## 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 ```