Files
deck_in_a_dash/docs/input-output-format.md
T

12 KiB

Input & output formats

See architecture.md for how these fit together. There are two ways to provide input (direct CLI flags, or run-engine.sh

  • user.properties) and three output formats (text, html, json); the DailyReading struct is the programmatic form all three are rendered from, and the one the future watchapp will consume directly. json is also interpreter-cli's input format — see "The interpreter" at the bottom of this file.

Input

Direct CLI flags (dist/deck-engine)

deck-engine --seed <string> --birth-date YYYY-MM-DD --birth-time HH:MM
            --birth-utc-offset <hours> --birth-lat <deg> --birth-lon <deg>
            [--date YYYY-MM-DDTHH:MM] [--format text|html|json]
Flag Required Format Meaning
--seed yes any string Tarot seed. The same seed always produces the same 10-card spread, positions, and orientations (see rng.c/tarot.c).
--birth-date yes YYYY-MM-DD Birth date, local calendar.
--birth-time yes HH:MM (24h) Birth time, local clock.
--birth-utc-offset yes decimal hours, e.g. 2 or -5.5 Hours to subtract from local birth time to get UTC (e.g. 2 for CEST).
--birth-lat yes decimal degrees Birth latitude, north positive.
--birth-lon yes decimal degrees Birth longitude, east positive.
--date no YYYY-MM-DDTHH:MM[:SS] UTC moment used for today's transits and as the moment the tarot seed is drawn against. Defaults to the current system time.
--format no text | html | json Output format, defaults to text. json always uses language-independent identifiers, ignoring --lang.
--lang no language code, e.g. en, de Reading language, defaults to en. Matches a file named <code>.lang in --i18n-dir (see "Translations" below). Unknown codes or missing files fall back to English with a warning on stderr.
--i18n-dir no path Directory to look for <lang>.lang in. Defaults to an i18n/ directory next to the binary itself (resolved from argv[0], so dist/deck-engine finds dist/i18n/ regardless of the caller's working directory).

Birth latitude/longitude only affect the Ascendant/houses — planet signs are geocentric and location-independent.

run-engine.sh + user.properties

dist/run-engine.sh [text|html|json] [lang] wraps the binary for daily use:

  • Seed and --date come from the OS clock: date +%Y-%m-%d (stable all day) and date -u +%Y-%m-%dT%H:%M.

  • Birth data and language are read from dist/user.properties, a key=value properties file next to the script (#-prefixed lines and blank lines are comments; whitespace around = is trimmed):

    birth_date=1990-05-14
    birth_time=14:32
    birth_utc_offset=2
    birth_lat=52.5200
    birth_lon=13.4050
    lang=en
    

    The file is seeded once from engine/scripts/user.properties.template with placeholder values (YOUR_BIRTH_DATE_HERE, etc.). run-engine.sh checks every birth field for an empty value or a leftover YOUR_ placeholder and refuses to run with a clear error until the file is filled in; lang has no such check since it always has a usable default (en). Since dist/ itself can be wiped (make clean, or deleting the directory), the build also keeps a durable backup at engine/scripts/user.properties.local (gitignored) once the file looks filled in, and restores from it if dist/user.properties is ever missing — see CLAUDE.md's "Build & run".

  • The optional second argument overrides lang= for a single run without editing the file, e.g. dist/run-engine.sh html de.

  • dist/run-interpreter.sh (no arguments) is the equivalent wrapper for the interpreter: same OS clock + user.properties inputs, but calls deck-engine --format json and pipes it straight into dist/interpreter-cli — see "The interpreter" below.

Translations (--lang, dist/i18n/)

Every piece of display text — planet/sign/moon-phase/aspect names, tarot card names and meanings, Celtic Cross position names, and the section headers in the text/html output — is looked up in a translation catalog with the built-in English text as the fallback for any key the catalog doesn't have. See CLAUDE.md's "Translations" section for the key-naming convention and how to add a new language.

engine/i18n/en.lang and de.lang are the shipped translation files; make copies engine/i18n/*.lang to dist/i18n/ (refreshed on every build, like dist/img/). Adding a language is just dropping another <code>.lang file into engine/i18n/ — no code changes needed.

Output

DailyReading (the real output — reading.h)

reading_generate() is the actual API; text/html below are just two ways of rendering the struct it fills in. This is what the watchapp will read directly once it exists.

typedef struct {
  NatalChart natal;
  DailyTransits transits;
  CelticCrossSpread spread;
} DailyReading;
  • NatalChart (astro.h): bodies[10] (Sun..Pluto, each a sign + degree-in-sign + raw ecliptic longitude + whole-sign house 1-12), ascendant_longitude, houses[12] (whole-sign: houses[0] is the Ascendant's sign).
  • DailyTransits (astro.h): bodies[10] (today's positions, same shape as above — house here is the natal chart's house the transiting planet currently occupies, not a fresh "houses for right now" chart), moon_phase (one of 8 named phases), aspects[] + aspect_count (each aspect names a transiting planet, a natal planet, an AspectType — conjunction/sextile/square/trine/opposition — and the orb in degrees).
  • CelticCrossSpread (tarot.h): positions[10], indexed by CelticCrossPosition (Present, Challenge, Crown, Foundation, Recent Past, Near Future, Attitude, Environment, Hopes and Fears, Outcome — Waite's own drawing order), each holding a TarotCard and a reversed bool.

--format text

Four =====-delimited sections, in order: natal chart (each body's sign

  • degree + house, then the Ascendant), today's sky (Moon phase, then each body's transiting position + the natal house it currently occupies), today's aspects to the natal chart (one line per aspect, with orb), and the Celtic Cross (one block per position: name, card, (Reversed) if applicable, the position's meaning, then the card's meaning).
===== Natal Chart =====
Sun       23.5° Taurus      (House 3)
Moon      15.2° Capricorn   (House 11)
...
Ascendant  18.4° Pisces

===== Today's Sky =====
Moon phase: Waning Gibbous
Sun       11.5° Cancer      (House 5)
...

===== Today's Aspects to Natal Chart =====
Transiting Sun Opposition natal Moon (orb 3.7°)
...

===== Celtic Cross =====
The Present      The High Priestess (Reversed)
  This covers him: the general influence affecting the matter.
  Passion, moral or physical ardour, conceit, surface knowledge.
...

--format html

A single self-contained HTML page (inline <style>, no JS, no external requests) with the same four sections, plus each of the 10 Celtic Cross cards rendered as an image:

<img class="reversed" src="img/RWS_Tarot_02_High_Priestess.jpeg" alt="The High Priestess">
  • src="img/<file>" is relative to the HTML file's own location — it expects to sit next to an img/ directory, exactly like dist/reading.html next to dist/img/ (which make images populates from res/img/). Saving the HTML output anywhere else breaks the image links.
  • Reversed cards get class="reversed", styled with transform: rotate(180deg) in the page's inline CSS.

--format json

A single JSON object mirroring DailyReading exactly — natal, transits, spread — using the slug accessors (astro_body_slug(), astro_sign_slug(), astro_moon_phase_slug(), astro_aspect_slug(), tarot_card_slug(), tarot_position_slug()) rather than the display-name ones, so the output is identical regardless of --lang — this is the one format meant for another program to parse (dist/interpreter-cli, see "The interpreter" below), not for a human to read.

{
  "natal": {
    "bodies": [
      {"body": "sun", "sign": "taurus", "degree_in_sign": 23.4926, "ecliptic_longitude": 53.4926, "house": 3},
      ...
    ],
    "ascendant_longitude": 348.4123,
    "houses": ["pisces", "aries", ...]
  },
  "transits": {
    "bodies": [
      {"body": "sun", "sign": "cancer", "degree_in_sign": 11.5012, "ecliptic_longitude": 101.5012, "house": 5},
      ...
    ],
    "moon_phase": "waning_gibbous",
    "aspects": [
      {"transiting_planet": "sun", "natal_planet": "moon", "type": "opposition", "orb": 3.7021},
      ...
    ]
  },
  "spread": {
    "positions": [
      {"position": "present", "card": "high_priestess", "reversed": true},
      ...
    ]
  }
}
  • bodies is always NUM_BODIES (10) entries, Sun..Pluto in Body enum order; spread.positions is always TAROT_SPREAD_SIZE (10) entries in CelticCrossPosition enum order (Present, Challenge, Crown, Foundation, Recent Past, Near Future, Attitude, Environment, Hopes and Fears, Outcome — see CLAUDE.md's "Tarot" section for why that's not the order most tutorials use).
  • natal.houses[i] is the sign occupying whole-sign house i + 1; houses[0] is the Ascendant's own sign. transits.bodies[*].house is a house number (1-12) into the natal chart, not a fresh "houses for right now" chart — same rule as the text/html output.
  • All angles are decimal degrees, %.4f. aspects may be an empty array (a valid "no aspects today"), but the key itself is always present.

The interpreter (interpreter/, dist/interpreter-cli)

dist/interpreter-cli is a separate binary — see CLAUDE.md's "Interpretation" section for the full architecture — that reads a --format json document and scores how significant today's transits are. It never links the engine; it only depends on the JSON shape above.

dist/deck-engine ... --format json | dist/interpreter-cli
# or:
dist/deck-engine ... --format json > reading.json
dist/interpreter-cli reading.json
# or, for daily use (OS clock + user.properties, same inputs as run-engine.sh):
dist/run-interpreter.sh
  • Input: a file path argument, or stdin if no argument (or -) is given. "transits"."aspects" is required (an empty array is valid and means "no notable transits today"; a missing key is treated as invalid input) and drives scoring. "natal"."bodies"/"transits"."bodies" are read too, for the sign/house context printed alongside each event below (not for scoring) — an entry naming an unrecognized body, or a missing house, is silently skipped rather than failing the load (see parse_bodies() in reading_io.c); spread is always ignored.

  • Output: a plain-text report — the day's overall significance level (Quiet/Notable/Significant/Major), a "worth a deeper Celtic Cross look" line on Major days only, and up to 5 of today's aspects ranked by score, each naming the sign and natal house the transiting planet currently occupies and the sign and house of the natal planet it's aspecting:

    Day significance: Significant
    (a major transit today - worth a deeper Celtic Cross look)
    
    Top 3 significant transits:
      1. Transiting Saturn in Aries (house 2) Square natal Sun in Taurus (house 3) (orb 0.5°, score 8.10)
      2. Transiting Pluto in Aquarius (house 12) Opposition natal Moon in Capricorn (house 11) (orb 0.2°, score 7.92)
      3. Transiting Jupiter in Leo (house 5) Trine natal Venus in Aries (house 2) (orb 2.1°, score 3.40)