Files
deck_in_a_dash/interpreter/src/narrative.c
T
ml 1a654da3e9
build / build (push) Successful in 30s
Introduces watch/, a real Pebble watchapp (built via pebble build) that
shows the daily tarot/astrology reading on-device: persistence, reading
computation, and UI screens, driven by the existing engine/interpreter
code linked in unchanged where possible.

Required engine-side changes to make that linking work:
- A compact low-precision ephemeris (lowprec_ephemeris.c) replacing the
  vendored ~127KB Astronomy Engine, which doesn't fit the watch's ~64KB
  app budget.
- Hand-rolled sqrt/atan2/sin/cos replacements for that ephemeris and
  astro.c's Ascendant calculation - Pebble's statically-linked libm
  hard-faults on real hardware under this app's -fPIE link for all four.
- narrative.c/guidance.c refactored from FILE*/fprintf onto snprintf-
  based buffers, since Pebble's SDK blocks fprintf at compile time.
2026-07-14 17:10:22 +02:00

183 lines
6.9 KiB
C

#include "narrative.h"
#include <stdbool.h>
#include <stdio.h>
/* i18n_get() only - narrative.h already pulls in astro.h; this is the
* one additional engine header needed to route k_narratives/
* k_house_area through the same translation catalog tarot_data.c uses
* (see the Makefile's INTERP_TAROT_TEXT_OBJS comment for why i18n.c is
* linked into this binary). */
#include "../../engine/src/i18n.h"
typedef struct {
const char *slug; /* builds "narrative.<slug>.*" i18n keys */
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;
/* Fallback (English) text, also the source of truth when no translation
* catalog is loaded - see narrative.h's doc comment for sourcing
* (Sepharial, "Transits and Planetary Periods", 1920, Chapter VIII) and
* the Moon/Pluto exception. German translations live in
* engine/i18n/de.lang under the same "narrative.<slug>.*" keys. */
static const TransitNarrative k_narratives[NUM_BODIES] = {
[PLANET_SUN] = {
"sun", "",
"Benefits from superiors and advancement in your sphere of "
"life and work - honours, emoluments, and successful new "
"associations.",
"Degradation and dishonour, loss of position, and adverse "
"judgement from superiors.",
},
[PLANET_MOON] = {
/* Original text, not from Sepharial - see narrative.h. */
"moon",
"Colours the everyday and domestic sphere of life, often "
"coinciding with the opening of new avenues.",
"These changes tend to be advantageous.",
"These changes tend to be adverse, with some indisposition "
"or domestic friction.",
},
[PLANET_MERCURY] = {
"mercury",
"Affects writings, journeys, commerce, and everyday activities - "
"a neutral messenger whose effect follows the nature of the "
"aspect it makes.",
"", "",
},
[PLANET_VENUS] = {
"venus",
"Brings domestic and social affairs to the fore - happiness, "
"comforts, and favours.",
"Success in love affairs and artistic pursuits is likely.",
"Grief and disappointment are more likely.",
},
[PLANET_MARS] = {
"mars",
"A strenuous time of quarrels, contention, strife and anger, with "
"some risk of hurts or injuries depending on the sign it "
"occupies.",
"Can bring benefits from doctors, surgeons, or new projects "
"and enterprises.",
"",
},
[PLANET_JUPITER] = {
"jupiter",
"Brings increase and expansion - fullness of fortune and health, "
"and a generally fortunate time.",
"", "",
},
[PLANET_SATURN] = {
"saturn",
"Brings depression, stagnation, hindrances and obstacles, and "
"some deprivation of the usual benefits.",
"Favours from older connections and past associations are "
"still possible.",
"",
},
[PLANET_URANUS] = {
"uranus",
"Brings separations, estrangements, sudden dislocations and "
"violent upsets.",
"Success through official or civic channels, and "
"beneficial changes or appointments, are possible.",
"",
},
[PLANET_NEPTUNE] = {
"neptune",
"Brings a state of chaos and confusion - an involved, uncertain "
"condition of affairs, with plots, subtlety, or unseen "
"influences at work.",
"", "",
},
[PLANET_PLUTO] = {
/* Original text, not from Sepharial - see narrative.h. */
"pluto",
"Brings deep, often hidden transformation - the surfacing or "
"dismantling of something that has outgrown its old form.",
"", "",
},
};
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; fallback (English) text only, keyed
* as "narrative.house.<1-12>" in engine/i18n's .lang files. */
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",
};
static const char *narrative_field(const char *slug, const char *field, const char *fallback) {
char key[48];
snprintf(key, sizeof key, "narrative.%s.%s", slug, field);
return i18n_get(key, fallback);
}
void narrative_write(char *buf, size_t buf_size, const char *indent, const Aspect *aspect,
int transiting_house) {
if (buf_size == 0) return;
buf[0] = '\0';
const TransitNarrative *n = &k_narratives[aspect->transiting_planet];
const char *base = narrative_field(n->slug, "base", n->base);
const char *extra = "";
if (aspect_is_harmonious(aspect->type)) extra = narrative_field(n->slug, "harmonious", n->harmonious);
else if (aspect_is_discordant(aspect->type)) extra = narrative_field(n->slug, "discordant", n->discordant);
if (base[0] == '\0' && extra[0] == '\0') return;
/* Chained snprintf() calls, each bounds-checked against the running
* length before the next one runs - never fprintf/sprintf/vsnprintf,
* all of which Pebble's SDK blocks at compile time (see narrative.h's
* doc comment). Once `len` reaches buf_size (truncated), every
* subsequent call is skipped rather than computing buf_size - len,
* which would underflow. */
size_t len = (size_t)snprintf(buf, buf_size, "%s", indent);
if (len >= buf_size) return;
if (transiting_house >= 1 && transiting_house <= 12) {
char house_key[24];
snprintf(house_key, sizeof house_key, "narrative.house.%d", transiting_house);
const char *area = i18n_get(house_key, k_house_area[transiting_house - 1]);
char frame[256];
snprintf(frame, sizeof frame, i18n_get("narrative.house_frame", "In matters of %s (house %d)."),
area, transiting_house);
len += (size_t)snprintf(buf + len, buf_size - len, "%s ", frame);
if (len >= buf_size) return;
}
if (base[0] != '\0') {
len += (size_t)snprintf(buf + len, buf_size - len, "%s", base);
if (len >= buf_size) return;
}
if (extra[0] != '\0') {
len += (size_t)snprintf(buf + len, buf_size - len, "%s%s", base[0] != '\0' ? " " : "", extra);
if (len >= buf_size) return;
}
snprintf(buf + len, buf_size - len, "\n");
}