Introduces watch/, a real Pebble watchapp (built via pebble build) that
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:
ml
2026-07-14 17:10:22 +02:00
parent d9b6e2889b
commit 1a654da3e9
44 changed files with 1920 additions and 182 deletions
+25 -20
View File
@@ -1,5 +1,3 @@
#define _POSIX_C_SOURCE 200809L /* for open_memstream */
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
@@ -156,27 +154,28 @@ static void print_body_in_sign_text(FILE *out, const PlanetPosition *pos) {
fprintf(out, " %s", buf);
}
/* Captures narrative_print()/guidance_print()'s output (they only know
* how to write to a FILE*) into a heap string, for embedding into JSON -
* avoids changing either module's public API just for this one caller.
* Strips a single trailing newline; caller must free() the result. */
/* Captures narrative_write()/guidance_write()'s output into a heap
* string, for embedding into JSON - a stack buffer plus a single
* malloc()+memcpy() copy, since main.c doesn't need to stream this
* anywhere. Strips a single trailing newline; caller must free() the
* result. */
static char *capture_narrative(const Aspect *aspect, int house) {
char *buf = NULL;
size_t size = 0;
FILE *mem = open_memstream(&buf, &size);
narrative_print(mem, "", aspect, house);
fclose(mem);
if (size > 0 && buf[size - 1] == '\n') buf[size - 1] = '\0';
char stack_buf[NARRATIVE_TEXT_MAX];
narrative_write(stack_buf, sizeof stack_buf, "", aspect, house);
size_t len = strlen(stack_buf);
if (len > 0 && stack_buf[len - 1] == '\n') stack_buf[--len] = '\0';
char *buf = malloc(len + 1);
memcpy(buf, stack_buf, len + 1);
return buf;
}
static char *capture_guidance(const DailyInterpretation *interp, const CelticCrossSpread *spread) {
char *buf = NULL;
size_t size = 0;
FILE *mem = open_memstream(&buf, &size);
guidance_print(mem, "", interp, spread);
fclose(mem);
if (size > 0 && buf[size - 1] == '\n') buf[size - 1] = '\0';
char stack_buf[GUIDANCE_TEXT_MAX];
guidance_write(stack_buf, sizeof stack_buf, "", interp, spread);
size_t len = strlen(stack_buf);
if (len > 0 && stack_buf[len - 1] == '\n') stack_buf[--len] = '\0';
char *buf = malloc(len + 1);
memcpy(buf, stack_buf, len + 1);
return buf;
}
@@ -228,7 +227,11 @@ static void interp_print_text(FILE *out, const DailyReading *reading, const Dail
print_body_in_sign_text(out, &reading->natal.bodies[a->natal_planet]);
fprintf(out, " (%s %.1f\xc2\xb0, %s %.2f)\n", ui("ui.orb", "orb"), a->orb,
ui("interp.score", "score"), item->score);
narrative_print(out, " ", a, reading->transits.bodies[a->transiting_planet].house);
char narrative_buf[NARRATIVE_TEXT_MAX];
narrative_write(narrative_buf, sizeof narrative_buf, " ", a,
reading->transits.bodies[a->transiting_planet].house);
fputs(narrative_buf, out);
}
}
fprintf(out, "\n");
@@ -236,7 +239,9 @@ static void interp_print_text(FILE *out, const DailyReading *reading, const Dail
print_celtic_cross_text(out, &reading->spread);
fprintf(out, "\n");
guidance_print(out, "", interp, &reading->spread);
char guidance_buf[GUIDANCE_TEXT_MAX];
guidance_write(guidance_buf, sizeof guidance_buf, "", interp, &reading->spread);
fputs(guidance_buf, out);
}
/* ===== --format html ===== */