Add narrative text and a day-significance rank to the interpreter

Each of the day's top significant transits now gets a short sentence
describing what it classically means and which area of life its house
governs, condensed from Sepharial's Transits and Planetary Periods
(1920, public domain) plus a small table of traditional house
significations. The day-level line also shows its rank out of the 4
defined levels, e.g. "Notable (2/4)". Docs (CLAUDE.md, README,
docs/input-output-format.md, docs/reading.md) updated to match,
including a full worked example in docs/reading.md.
This commit is contained in:
ml
2026-07-05 21:05:53 +02:00
parent 1136e3257a
commit 41422adf98
11 changed files with 476 additions and 41 deletions
+5 -2
View File
@@ -3,6 +3,7 @@
#include <stdlib.h>
#include <string.h>
#include "narrative.h"
#include "reading_io.h"
#include "significance.h"
@@ -12,7 +13,7 @@ static void print_usage(const char *prog) {
"Reads a DailyReading in deck-engine's --format json shape - from the\n"
"given file, or from stdin if no file is given (or it is \"-\") - and\n"
"prints a significance report: the day's overall level and the top\n"
"aspects driving it.\n\n"
"aspects driving it, each with a short narrative note.\n\n"
"Typical use:\n"
" dist/deck-engine ... --format json | %s\n"
" dist/deck-engine ... --format json > reading.json && %s reading.json\n",
@@ -115,7 +116,8 @@ int main(int argc, char **argv) {
DailyInterpretation interp;
interpret_daily_reading(&reading, &interp);
printf("Day significance: %s\n", day_level_name(interp.day_level));
printf("Day significance: %s (%d/%d)\n", day_level_name(interp.day_level),
interp.day_level + 1, DAY_SIGNIFICANCE_COUNT);
if (interpretation_deserves_framing(&interp)) {
printf("(a major transit today - worth a deeper Celtic Cross look)\n");
}
@@ -137,6 +139,7 @@ int main(int argc, char **argv) {
printf(" %s natal %s", aspect_display_name(a->type), body_display_name(a->natal_planet));
print_body_in_sign(&reading.natal.bodies[a->natal_planet]);
printf(" (orb %.1f\xc2\xb0, score %.2f)\n", a->orb, item->score);
narrative_print(stdout, " ", a, reading.transits.bodies[a->transiting_planet].house);
}
return 0;
}
+133
View File
@@ -0,0 +1,133 @@
#include "narrative.h"
#include <stdbool.h>
typedef struct {
const char *base; /* always relevant, may be "" (see the Sun) */
const char *harmonious; /* shown only under a trine/sextile; may be "" */
const char *discordant; /* shown only under a square/opposition; may be "" */
} TransitNarrative;
/* See narrative.h's doc comment for sourcing (Sepharial, "Transits and
* Planetary Periods", 1920, Chapter VIII) and the Moon/Pluto exception. */
static const TransitNarrative k_narratives[NUM_BODIES] = {
[PLANET_SUN] = {
.base = "",
.harmonious = "Benefits from superiors and advancement in your sphere of "
"life and work - honours, emoluments, and successful new "
"associations.",
.discordant = "Degradation and dishonour, loss of position, and adverse "
"judgement from superiors.",
},
[PLANET_MOON] = {
/* Original text, not from Sepharial - see narrative.h. */
.base = "Colours the everyday and domestic sphere of life, often "
"coinciding with the opening of new avenues.",
.harmonious = "These changes tend to be advantageous.",
.discordant = "These changes tend to be adverse, with some indisposition "
"or domestic friction.",
},
[PLANET_MERCURY] = {
.base = "Affects writings, journeys, commerce, and everyday activities - "
"a neutral messenger whose effect follows the nature of the "
"aspect it makes.",
.harmonious = "",
.discordant = "",
},
[PLANET_VENUS] = {
.base = "Brings domestic and social affairs to the fore - happiness, "
"comforts, and favours.",
.harmonious = "Success in love affairs and artistic pursuits is likely.",
.discordant = "Grief and disappointment are more likely.",
},
[PLANET_MARS] = {
.base = "A strenuous time of quarrels, contention, strife and anger, with "
"some risk of hurts or injuries depending on the sign it "
"occupies.",
.harmonious = "Can bring benefits from doctors, surgeons, or new projects "
"and enterprises.",
.discordant = "",
},
[PLANET_JUPITER] = {
.base = "Brings increase and expansion - fullness of fortune and health, "
"and a generally fortunate time.",
.harmonious = "",
.discordant = "",
},
[PLANET_SATURN] = {
.base = "Brings depression, stagnation, hindrances and obstacles, and "
"some deprivation of the usual benefits.",
.harmonious = "Favours from older connections and past associations are "
"still possible.",
.discordant = "",
},
[PLANET_URANUS] = {
.base = "Brings separations, estrangements, sudden dislocations and "
"violent upsets.",
.harmonious = "Success through official or civic channels, and "
"beneficial changes or appointments, are possible.",
.discordant = "",
},
[PLANET_NEPTUNE] = {
.base = "Brings a state of chaos and confusion - an involved, uncertain "
"condition of affairs, with plots, subtlety, or unseen "
"influences at work.",
.harmonious = "",
.discordant = "",
},
[PLANET_PLUTO] = {
/* Original text, not from Sepharial - see narrative.h. */
.base = "Brings deep, often hidden transformation - the surfacing or "
"dismantling of something that has outgrown its old form.",
.harmonious = "",
.discordant = "",
},
};
static bool aspect_is_harmonious(AspectType type) {
return type == ASPECT_TRINE || type == ASPECT_SEXTILE;
}
static bool aspect_is_discordant(AspectType type) {
return type == ASPECT_SQUARE || type == ASPECT_OPPOSITION;
}
/* Traditional whole-sign house significations - the "area of life" each
* house governs. This is centuries-old, uncredited astrological
* convention (the same status as the sign/aspect names already baked
* into engine/src/astro.c), not text drawn from Sepharial or any other
* single source. Indexed house - 1. */
static const char *const k_house_area[12] = {
"your sense of self, identity, and outward appearance",
"money, possessions, and personal values",
"communication, siblings, and everyday learning",
"home, family, and your roots",
"romance, creativity, and children",
"daily work, routine, and health",
"partnerships and close relationships",
"shared resources, intimacy, and transformation",
"travel, higher learning, and beliefs",
"career, reputation, and public standing",
"friendships, community, and hopes for the future",
"solitude, the subconscious, and hidden matters",
};
void narrative_print(FILE *out, const char *indent, const Aspect *aspect,
int transiting_house) {
const TransitNarrative *n = &k_narratives[aspect->transiting_planet];
const char *extra = "";
if (aspect_is_harmonious(aspect->type)) extra = n->harmonious;
else if (aspect_is_discordant(aspect->type)) extra = n->discordant;
if (n->base[0] == '\0' && extra[0] == '\0') return;
fprintf(out, "%s", indent);
if (transiting_house >= 1 && transiting_house <= 12) {
fprintf(out, "In matters of %s (house %d). ",
k_house_area[transiting_house - 1], transiting_house);
}
if (n->base[0] != '\0') fprintf(out, "%s", n->base);
if (extra[0] != '\0') fprintf(out, "%s%s", n->base[0] != '\0' ? " " : "", extra);
fprintf(out, "\n");
}
+46
View File
@@ -0,0 +1,46 @@
#ifndef DECK_NARRATIVE_H
#define DECK_NARRATIVE_H
#include <stdio.h>
/* Header-only dependency on the engine, same policy as significance.h -
* see its comment for why. */
#include "../../engine/src/astro.h"
/* Condensed, public-domain descriptions of what each transiting planet
* classically signifies, sourced from Sepharial's "Transits and
* Planetary Periods" (1920, public domain - res/Transits_and_Planetary_
* Periods.pdf), Chapter VIII "Effects of Transits" (pp. 60-61). The
* chapter's own structure - a base description of the planet's transit,
* plus a "well aspected"/"badly aspected" addendum - maps directly onto
* transiting-planet + aspect-type, our existing Aspect fields, so no new
* engine data is needed to use it.
*
* The Moon and Pluto aren't covered by that chapter (fast lunar transits
* were outside a year-ahead-forecasting book's scope; Pluto wasn't
* discovered until 1930, a decade after this book) - their text in
* narrative.c is original, written for this project in a matching
* voice, not drawn from Sepharial.
*
* Prints a narrative sentence for `aspect`'s transiting planet to `out`,
* indented with `indent` and followed by a newline - or prints nothing
* if the source material has no text applicable to this planet/aspect
* combination (e.g. an exactly neutral conjunction to a planet whose
* only text is a "well/badly aspected" addendum, like the Sun). A
* conjunction is treated as neutral - neither the "harmonious" nor the
* "discordant" addendum is shown for it - since Sepharial's own text
* says a conjunction "takes the nature of what it conjoins," something
* this data doesn't model.
*
* `transiting_house` is the whole-sign house (1-12) the transiting
* planet currently occupies in the natal chart - i.e.
* reading->transits.bodies[aspect->transiting_planet].house - and names
* *which area of life* the transit is playing out in, the standard,
* personalized way transits are read (see astro.h's own house-field
* comment). Pass a value outside 1-12 (e.g. the "no data" sentinel `0`
* used elsewhere - see reading_io.c's parse_bodies()) to omit that
* framing and print the planet's narrative on its own. */
void narrative_print(FILE *out, const char *indent, const Aspect *aspect,
int transiting_house);
#endif
+4 -1
View File
@@ -34,7 +34,10 @@ typedef enum {
DAY_QUIET = 0,
DAY_NOTABLE,
DAY_SIGNIFICANT,
DAY_MAJOR
DAY_MAJOR,
DAY_SIGNIFICANCE_COUNT /* not a real level - the number of levels above,
* for callers that want to show day_level as a
* "rank out of N" (e.g. main.c's report). */
} DaySignificance;
typedef struct {