Introduces watch/, a real Pebble watchapp (built via pebble build) that
build / build (push) Successful in 30s
build / build (push) Successful in 30s
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.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#define _POSIX_C_SOURCE 200809L /* for mkstemp/fdopen */
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -9,15 +10,6 @@
|
||||
#include "../../engine/src/i18n.h"
|
||||
#include "../src/guidance.h"
|
||||
|
||||
static const char *slurp(FILE *f) {
|
||||
static char buf[4096];
|
||||
long len = ftell(f);
|
||||
rewind(f);
|
||||
size_t n = fread(buf, 1, (size_t)len, f);
|
||||
buf[n] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
static DailyInterpretation make_interp(DaySignificance level, AspectType type, Body transiting) {
|
||||
DailyInterpretation interp;
|
||||
interp.top_item_count = 1;
|
||||
@@ -51,9 +43,8 @@ static CelticCrossSpread make_spread(TarotCard attitude_card, bool attitude_reve
|
||||
static void test_harmonious_upright(void) {
|
||||
DailyInterpretation interp = make_interp(DAY_MAJOR, ASPECT_TRINE, PLANET_JUPITER);
|
||||
CelticCrossSpread spread = make_spread(CARD_SUN, false, CARD_WORLD, false);
|
||||
FILE *f = tmpfile();
|
||||
guidance_print(f, "", &interp, &spread);
|
||||
const char *text = slurp(f);
|
||||
char text[GUIDANCE_TEXT_MAX];
|
||||
guidance_write(text, sizeof text, "", &interp, &spread);
|
||||
|
||||
assert(strstr(text, "already meeting the day in the right spirit") != NULL);
|
||||
assert(strstr(text, "Jupiter") != NULL);
|
||||
@@ -61,39 +52,34 @@ static void test_harmonious_upright(void) {
|
||||
assert(strstr(text, "The World: Assured success") != NULL);
|
||||
assert(strstr(text, "(Reversed)") == NULL);
|
||||
|
||||
fclose(f);
|
||||
printf("PASS test_harmonious_upright\n");
|
||||
}
|
||||
|
||||
static void test_discordant_reversed(void) {
|
||||
DailyInterpretation interp = make_interp(DAY_MAJOR, ASPECT_SQUARE, PLANET_SATURN);
|
||||
CelticCrossSpread spread = make_spread(CARD_DEVIL, true, CARD_WORLD, false);
|
||||
FILE *f = tmpfile();
|
||||
guidance_print(f, "", &interp, &spread);
|
||||
const char *text = slurp(f);
|
||||
char text[GUIDANCE_TEXT_MAX];
|
||||
guidance_write(text, sizeof text, "", &interp, &spread);
|
||||
|
||||
assert(strstr(text, "Both the day and your own footing are unsettled") != NULL);
|
||||
assert(strstr(text, "Saturn") != NULL);
|
||||
assert(strstr(text, "The Devil (Reversed): Evil fatality") != NULL);
|
||||
assert(strstr(text, "The World: Assured success") != NULL);
|
||||
|
||||
fclose(f);
|
||||
printf("PASS test_discordant_reversed\n");
|
||||
}
|
||||
|
||||
static void test_neutral_conjunction_upright(void) {
|
||||
DailyInterpretation interp = make_interp(DAY_MAJOR, ASPECT_CONJUNCTION, PLANET_PLUTO);
|
||||
CelticCrossSpread spread = make_spread(CARD_EMPEROR, false, CARD_MOON, true);
|
||||
FILE *f = tmpfile();
|
||||
guidance_print(f, "", &interp, &spread);
|
||||
const char *text = slurp(f);
|
||||
char text[GUIDANCE_TEXT_MAX];
|
||||
guidance_write(text, sizeof text, "", &interp, &spread);
|
||||
|
||||
assert(strstr(text, "Today concentrates whatever you already bring to it") != NULL);
|
||||
assert(strstr(text, "Pluto") != NULL);
|
||||
assert(strstr(text, "The Emperor: Stability, power") != NULL);
|
||||
assert(strstr(text, "The Moon (Reversed): Instability, inconstancy") != NULL);
|
||||
|
||||
fclose(f);
|
||||
printf("PASS test_neutral_conjunction_upright\n");
|
||||
}
|
||||
|
||||
@@ -104,74 +90,64 @@ static void test_neutral_conjunction_upright(void) {
|
||||
static void test_significant_day_uses_significant_intro(void) {
|
||||
DailyInterpretation interp = make_interp(DAY_SIGNIFICANT, ASPECT_SEXTILE, PLANET_VENUS);
|
||||
CelticCrossSpread spread = make_spread(CARD_STAR, false, CARD_SUN, false);
|
||||
FILE *f = tmpfile();
|
||||
guidance_print(f, "", &interp, &spread);
|
||||
const char *text = slurp(f);
|
||||
char text[GUIDANCE_TEXT_MAX];
|
||||
guidance_write(text, sizeof text, "", &interp, &spread);
|
||||
|
||||
assert(strstr(text, "clearly active today") != NULL);
|
||||
assert(strstr(text, "Venus") != NULL);
|
||||
|
||||
fclose(f);
|
||||
printf("PASS test_significant_day_uses_significant_intro\n");
|
||||
}
|
||||
|
||||
static void test_notable_day_uses_notable_intro(void) {
|
||||
DailyInterpretation interp = make_interp(DAY_NOTABLE, ASPECT_SEXTILE, PLANET_MARS);
|
||||
CelticCrossSpread spread = make_spread(CARD_STAR, false, CARD_SUN, false);
|
||||
FILE *f = tmpfile();
|
||||
guidance_print(f, "", &interp, &spread);
|
||||
const char *text = slurp(f);
|
||||
char text[GUIDANCE_TEXT_MAX];
|
||||
guidance_write(text, sizeof text, "", &interp, &spread);
|
||||
|
||||
assert(strstr(text, "mild touch from Mars") != NULL);
|
||||
|
||||
fclose(f);
|
||||
printf("PASS test_notable_day_uses_notable_intro\n");
|
||||
}
|
||||
|
||||
static void test_quiet_day_with_a_faint_item_still_names_it(void) {
|
||||
DailyInterpretation interp = make_interp(DAY_QUIET, ASPECT_SEXTILE, PLANET_MERCURY);
|
||||
CelticCrossSpread spread = make_spread(CARD_STAR, false, CARD_SUN, false);
|
||||
FILE *f = tmpfile();
|
||||
guidance_print(f, "", &interp, &spread);
|
||||
const char *text = slurp(f);
|
||||
char text[GUIDANCE_TEXT_MAX];
|
||||
guidance_write(text, sizeof text, "", &interp, &spread);
|
||||
|
||||
assert(strstr(text, "only faintly active today") != NULL);
|
||||
assert(strstr(text, "Mercury") != NULL);
|
||||
|
||||
fclose(f);
|
||||
printf("PASS test_quiet_day_with_a_faint_item_still_names_it\n");
|
||||
}
|
||||
|
||||
static void test_no_aspects_at_all_falls_back_to_attitude_only(void) {
|
||||
DailyInterpretation interp = make_empty_interp();
|
||||
CelticCrossSpread spread = make_spread(CARD_STAR, false, CARD_SUN, false);
|
||||
FILE *f = tmpfile();
|
||||
guidance_print(f, "", &interp, &spread);
|
||||
const char *text = slurp(f);
|
||||
char text[GUIDANCE_TEXT_MAX];
|
||||
guidance_write(text, sizeof text, "", &interp, &spread);
|
||||
|
||||
assert(strstr(text, "astrologically quiet day") != NULL);
|
||||
assert(strstr(text, "trust your current footing") != NULL);
|
||||
assert(strstr(text, "The Star: Loss, theft") != NULL);
|
||||
assert(strstr(text, "The Sun: Material happiness") != NULL);
|
||||
|
||||
fclose(f);
|
||||
printf("PASS test_no_aspects_at_all_falls_back_to_attitude_only\n");
|
||||
}
|
||||
|
||||
static void test_no_aspects_at_all_respects_reversed_attitude(void) {
|
||||
DailyInterpretation interp = make_empty_interp();
|
||||
CelticCrossSpread spread = make_spread(CARD_STAR, true, CARD_SUN, false);
|
||||
FILE *f = tmpfile();
|
||||
guidance_print(f, "", &interp, &spread);
|
||||
const char *text = slurp(f);
|
||||
char text[GUIDANCE_TEXT_MAX];
|
||||
guidance_write(text, sizeof text, "", &interp, &spread);
|
||||
|
||||
assert(strstr(text, "quietly re-settle your own footing") != NULL);
|
||||
|
||||
fclose(f);
|
||||
printf("PASS test_no_aspects_at_all_respects_reversed_attitude\n");
|
||||
}
|
||||
|
||||
/* Proves guidance_print() actually routes through i18n_get() rather
|
||||
/* Proves guidance_write() actually routes through i18n_get() rather
|
||||
* than just printing the hardcoded fallback. Run last: i18n_load()
|
||||
* replaces the process-global catalog for the rest of the binary's
|
||||
* lifetime. */
|
||||
@@ -191,16 +167,14 @@ static void test_translation_catalog_overrides_fallback_text(void) {
|
||||
|
||||
DailyInterpretation interp = make_interp(DAY_MAJOR, ASPECT_SQUARE, PLANET_SATURN);
|
||||
CelticCrossSpread spread = make_spread(CARD_SUN, false, CARD_WORLD, false);
|
||||
FILE *out = tmpfile();
|
||||
guidance_print(out, "", &interp, &spread);
|
||||
const char *text = slurp(out);
|
||||
char text[GUIDANCE_TEXT_MAX];
|
||||
guidance_write(text, sizeof text, "", &interp, &spread);
|
||||
|
||||
assert(strstr(text, "UEBERSETZTE HALTUNG") != NULL);
|
||||
assert(strstr(text, "UEBERSETZTE EINLEITUNG UEBERSETZTER SATURN") != NULL);
|
||||
assert(strstr(text, "UEBERSETZTE HALTUNGSKARTE") != NULL);
|
||||
assert(strstr(text, "Your instincts are sound") == NULL); /* English fallback must not leak through */
|
||||
|
||||
fclose(out);
|
||||
printf("PASS test_translation_catalog_overrides_fallback_text\n");
|
||||
}
|
||||
|
||||
|
||||
@@ -9,115 +9,92 @@
|
||||
#include "../../engine/src/i18n.h"
|
||||
#include "../src/narrative.h"
|
||||
|
||||
static const char *slurp(FILE *f) {
|
||||
static char buf[4096];
|
||||
long len = ftell(f);
|
||||
rewind(f);
|
||||
size_t n = fread(buf, 1, (size_t)len, f);
|
||||
buf[n] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void test_hard_aspect_shows_base_but_not_harmonious_bonus(void) {
|
||||
Aspect a = { .transiting_planet = PLANET_SATURN, .natal_planet = PLANET_MOON,
|
||||
.type = ASPECT_SQUARE, .orb = 1.0 };
|
||||
FILE *f = tmpfile();
|
||||
narrative_print(f, " ", &a, 0);
|
||||
const char *text = slurp(f);
|
||||
char text[NARRATIVE_TEXT_MAX];
|
||||
narrative_write(text, sizeof text, " ", &a, 0);
|
||||
|
||||
assert(strstr(text, "depression") != NULL);
|
||||
assert(strstr(text, "past associations") == NULL);
|
||||
|
||||
fclose(f);
|
||||
printf("PASS test_hard_aspect_shows_base_but_not_harmonious_bonus\n");
|
||||
}
|
||||
|
||||
static void test_soft_aspect_adds_harmonious_bonus(void) {
|
||||
Aspect a = { .transiting_planet = PLANET_SATURN, .natal_planet = PLANET_SUN,
|
||||
.type = ASPECT_TRINE, .orb = 1.0 };
|
||||
FILE *f = tmpfile();
|
||||
narrative_print(f, " ", &a, 0);
|
||||
const char *text = slurp(f);
|
||||
char text[NARRATIVE_TEXT_MAX];
|
||||
narrative_write(text, sizeof text, " ", &a, 0);
|
||||
|
||||
assert(strstr(text, "depression") != NULL);
|
||||
assert(strstr(text, "past associations") != NULL);
|
||||
|
||||
fclose(f);
|
||||
printf("PASS test_soft_aspect_adds_harmonious_bonus\n");
|
||||
}
|
||||
|
||||
static void test_conjunction_is_neutral_prints_base_only(void) {
|
||||
Aspect a = { .transiting_planet = PLANET_SATURN, .natal_planet = PLANET_SUN,
|
||||
.type = ASPECT_CONJUNCTION, .orb = 1.0 };
|
||||
FILE *f = tmpfile();
|
||||
narrative_print(f, " ", &a, 0);
|
||||
const char *text = slurp(f);
|
||||
char text[NARRATIVE_TEXT_MAX];
|
||||
narrative_write(text, sizeof text, " ", &a, 0);
|
||||
|
||||
assert(strstr(text, "depression") != NULL);
|
||||
assert(strstr(text, "past associations") == NULL);
|
||||
|
||||
fclose(f);
|
||||
printf("PASS test_conjunction_is_neutral_prints_base_only\n");
|
||||
}
|
||||
|
||||
/* The Sun has no unconditional base text (see narrative.c) - a neutral
|
||||
* conjunction to it should print nothing at all. */
|
||||
* conjunction to it should write nothing at all. */
|
||||
static void test_empty_base_and_neutral_aspect_prints_nothing(void) {
|
||||
Aspect a = { .transiting_planet = PLANET_SUN, .natal_planet = PLANET_MOON,
|
||||
.type = ASPECT_CONJUNCTION, .orb = 1.0 };
|
||||
FILE *f = tmpfile();
|
||||
narrative_print(f, " ", &a, 0);
|
||||
long len = ftell(f);
|
||||
char text[NARRATIVE_TEXT_MAX];
|
||||
narrative_write(text, sizeof text, " ", &a, 0);
|
||||
|
||||
assert(len == 0);
|
||||
assert(text[0] == '\0');
|
||||
|
||||
fclose(f);
|
||||
printf("PASS test_empty_base_and_neutral_aspect_prints_nothing\n");
|
||||
}
|
||||
|
||||
static void test_discordant_aspect_on_empty_base_planet(void) {
|
||||
Aspect a = { .transiting_planet = PLANET_SUN, .natal_planet = PLANET_MOON,
|
||||
.type = ASPECT_OPPOSITION, .orb = 1.0 };
|
||||
FILE *f = tmpfile();
|
||||
narrative_print(f, " ", &a, 0);
|
||||
const char *text = slurp(f);
|
||||
char text[NARRATIVE_TEXT_MAX];
|
||||
narrative_write(text, sizeof text, " ", &a, 0);
|
||||
|
||||
assert(strstr(text, "Degradation") != NULL);
|
||||
|
||||
fclose(f);
|
||||
printf("PASS test_discordant_aspect_on_empty_base_planet\n");
|
||||
}
|
||||
|
||||
static void test_known_house_adds_area_framing(void) {
|
||||
Aspect a = { .transiting_planet = PLANET_SATURN, .natal_planet = PLANET_MOON,
|
||||
.type = ASPECT_SQUARE, .orb = 1.0 };
|
||||
FILE *f = tmpfile();
|
||||
narrative_print(f, " ", &a, 3);
|
||||
const char *text = slurp(f);
|
||||
char text[NARRATIVE_TEXT_MAX];
|
||||
narrative_write(text, sizeof text, " ", &a, 3);
|
||||
|
||||
assert(strstr(text, "house 3") != NULL);
|
||||
assert(strstr(text, "communication") != NULL);
|
||||
assert(strstr(text, "depression") != NULL);
|
||||
|
||||
fclose(f);
|
||||
printf("PASS test_known_house_adds_area_framing\n");
|
||||
}
|
||||
|
||||
static void test_unknown_house_omits_area_framing(void) {
|
||||
Aspect a = { .transiting_planet = PLANET_SATURN, .natal_planet = PLANET_MOON,
|
||||
.type = ASPECT_SQUARE, .orb = 1.0 };
|
||||
FILE *f = tmpfile();
|
||||
narrative_print(f, " ", &a, 0); /* 0 = "no data" sentinel, not a real house */
|
||||
const char *text = slurp(f);
|
||||
char text[NARRATIVE_TEXT_MAX];
|
||||
narrative_write(text, sizeof text, " ", &a, 0); /* 0 = "no data" sentinel, not a real house */
|
||||
|
||||
assert(strstr(text, "house") == NULL);
|
||||
assert(strstr(text, "depression") != NULL);
|
||||
|
||||
fclose(f);
|
||||
printf("PASS test_unknown_house_omits_area_framing\n");
|
||||
}
|
||||
|
||||
/* Proves narrative_print() actually routes through i18n_get() rather
|
||||
/* Proves narrative_write() actually routes through i18n_get() rather
|
||||
* than just printing the hardcoded fallback - loads a translation file
|
||||
* that overrides one planet's base text and one house's area text, and
|
||||
* checks the override wins. Run last: i18n_load() replaces the
|
||||
@@ -136,15 +113,13 @@ static void test_translation_catalog_overrides_fallback_text(void) {
|
||||
|
||||
Aspect a = { .transiting_planet = PLANET_SATURN, .natal_planet = PLANET_MOON,
|
||||
.type = ASPECT_CONJUNCTION, .orb = 1.0 };
|
||||
FILE *out = tmpfile();
|
||||
narrative_print(out, "", &a, 3);
|
||||
const char *text = slurp(out);
|
||||
char text[NARRATIVE_TEXT_MAX];
|
||||
narrative_write(text, sizeof text, "", &a, 3);
|
||||
|
||||
assert(strstr(text, "UEBERSETZTER SATURN TEXT") != NULL);
|
||||
assert(strstr(text, "UEBERSETZTES HAUS DREI") != NULL);
|
||||
assert(strstr(text, "depression") == NULL); /* English fallback must not leak through */
|
||||
|
||||
fclose(out);
|
||||
printf("PASS test_translation_catalog_overrides_fallback_text\n");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user