Add astrological houses to natal chart and daily transits
Every planet position now carries its whole-sign house (1-12); transits report the natal house each transiting planet currently occupies rather than a fresh "houses for right now" chart, matching how transits are normally read. Surfaced in both text and HTML output, fully translated.
This commit is contained in:
@@ -120,6 +120,13 @@ you touch this function, keep the intermediate type 64-bit.
|
|||||||
Engine has no Ascendant function; it's computed directly in `astro.c`
|
Engine has no Ascendant function; it's computed directly in `astro.c`
|
||||||
from sidereal time + a standard low-precision obliquity polynomial +
|
from sidereal time + a standard low-precision obliquity polynomial +
|
||||||
the RAMC/obliquity/latitude identity (Duffett-Smith & Zwart).
|
the RAMC/obliquity/latitude identity (Duffett-Smith & Zwart).
|
||||||
|
- Every `PlanetPosition` (`astro.h`) carries a whole-sign `house` (1-12),
|
||||||
|
via `assign_houses()`/`whole_sign_house()`. **`DailyTransits.bodies[*].house`
|
||||||
|
is always relative to the *natal* Ascendant**, not a fresh "houses for
|
||||||
|
right now" chart — that's the standard, personalized way transits are
|
||||||
|
read (e.g. "transiting Jupiter is in your 5th house"), and it's why
|
||||||
|
`astro_compute_daily_transits` takes the natal chart as a parameter in
|
||||||
|
the first place (also used for aspect detection).
|
||||||
- Aspects (conjunction/sextile/square/trine/opposition) use an 8° orb
|
- Aspects (conjunction/sextile/square/trine/opposition) use an 8° orb
|
||||||
when a luminary (Sun/Moon) is involved, 6° otherwise.
|
when a luminary (Sun/Moon) is involved, 6° otherwise.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="res/logo.png" width="200" alt="Deck in a Dash logo">
|
<img src="res/logo.png" width="290" alt="Deck in a Dash logo">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# Deck in a Dash
|
# Deck in a Dash
|
||||||
|
|||||||
+14
-11
@@ -91,10 +91,13 @@ typedef struct {
|
|||||||
```
|
```
|
||||||
|
|
||||||
- **`NatalChart`** (`astro.h`): `bodies[10]` (Sun..Pluto, each a sign +
|
- **`NatalChart`** (`astro.h`): `bodies[10]` (Sun..Pluto, each a sign +
|
||||||
degree-in-sign + raw ecliptic longitude), `ascendant_longitude`,
|
degree-in-sign + raw ecliptic longitude + whole-sign `house` 1-12),
|
||||||
`houses[12]` (whole-sign: `houses[0]` is the Ascendant's sign).
|
`ascendant_longitude`, `houses[12]` (whole-sign: `houses[0]` is the
|
||||||
|
Ascendant's sign).
|
||||||
- **`DailyTransits`** (`astro.h`): `bodies[10]` (today's positions, same
|
- **`DailyTransits`** (`astro.h`): `bodies[10]` (today's positions, same
|
||||||
shape as above), `moon_phase` (one of 8 named phases), `aspects[]` +
|
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,
|
`aspect_count` (each aspect names a transiting planet, a natal planet,
|
||||||
an `AspectType` — conjunction/sextile/square/trine/opposition — and
|
an `AspectType` — conjunction/sextile/square/trine/opposition — and
|
||||||
the orb in degrees).
|
the orb in degrees).
|
||||||
@@ -107,22 +110,22 @@ typedef struct {
|
|||||||
### `--format text`
|
### `--format text`
|
||||||
|
|
||||||
Four `=====`-delimited sections, in order: natal chart (each body's sign
|
Four `=====`-delimited sections, in order: natal chart (each body's sign
|
||||||
+ degree, then the Ascendant), today's sky (Moon phase, then each body's
|
+ degree + house, then the Ascendant), today's sky (Moon phase, then each
|
||||||
transiting position), today's aspects to the natal chart (one line per
|
body's transiting position + the natal house it currently occupies),
|
||||||
aspect, with orb), and the Celtic Cross (one block per position: name,
|
today's aspects to the natal chart (one line per aspect, with orb), and
|
||||||
card, `(Reversed)` if applicable, the position's meaning, then the
|
the Celtic Cross (one block per position: name, card, `(Reversed)` if
|
||||||
card's meaning).
|
applicable, the position's meaning, then the card's meaning).
|
||||||
|
|
||||||
```
|
```
|
||||||
===== Natal Chart =====
|
===== Natal Chart =====
|
||||||
Sun 23.5° Taurus
|
Sun 23.5° Taurus (House 3)
|
||||||
Moon 15.2° Capricorn
|
Moon 15.2° Capricorn (House 11)
|
||||||
...
|
...
|
||||||
Ascendant 18.4° Pisces
|
Ascendant 18.4° Pisces
|
||||||
|
|
||||||
===== Today's Sky =====
|
===== Today's Sky =====
|
||||||
Moon phase: Waning Gibbous
|
Moon phase: Waning Gibbous
|
||||||
Sun 11.5° Cancer
|
Sun 11.5° Cancer (House 5)
|
||||||
...
|
...
|
||||||
|
|
||||||
===== Today's Aspects to Natal Chart =====
|
===== Today's Aspects to Natal Chart =====
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ ui.todays_sky=Heutiger Himmel
|
|||||||
ui.todays_aspects=Heutige Aspekte zum Geburtshoroskop
|
ui.todays_aspects=Heutige Aspekte zum Geburtshoroskop
|
||||||
ui.celtic_cross=Keltisches Kreuz
|
ui.celtic_cross=Keltisches Kreuz
|
||||||
ui.ascendant=Aszendent
|
ui.ascendant=Aszendent
|
||||||
|
ui.house=Haus
|
||||||
ui.moon_phase=Mondphase:
|
ui.moon_phase=Mondphase:
|
||||||
ui.no_aspects=(keine im Orbis)
|
ui.no_aspects=(keine im Orbis)
|
||||||
ui.reversed=(Umgekehrt)
|
ui.reversed=(Umgekehrt)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ ui.todays_sky=Today's Sky
|
|||||||
ui.todays_aspects=Today's Aspects to Natal Chart
|
ui.todays_aspects=Today's Aspects to Natal Chart
|
||||||
ui.celtic_cross=Celtic Cross
|
ui.celtic_cross=Celtic Cross
|
||||||
ui.ascendant=Ascendant
|
ui.ascendant=Ascendant
|
||||||
|
ui.house=House
|
||||||
ui.moon_phase=Moon phase:
|
ui.moon_phase=Moon phase:
|
||||||
ui.no_aspects=(none within orb)
|
ui.no_aspects=(none within orb)
|
||||||
ui.reversed=(Reversed)
|
ui.reversed=(Reversed)
|
||||||
|
|||||||
@@ -86,6 +86,20 @@ static void compute_body_positions(astro_time_t time, PlanetPosition out[NUM_BOD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Whole-sign house number (1-12) for `sign`, counting from `ascendant_sign`
|
||||||
|
* as house 1. Used both for the natal chart's own placements and (with the
|
||||||
|
* natal ascendant) for today's transiting planets - see the `house` field
|
||||||
|
* comment in astro.h for why transits use the natal houses, not their own. */
|
||||||
|
static int whole_sign_house(ZodiacSign sign, ZodiacSign ascendant_sign) {
|
||||||
|
return ((int)sign - (int)ascendant_sign + 12) % 12 + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assign_houses(PlanetPosition bodies[NUM_BODIES], ZodiacSign ascendant_sign) {
|
||||||
|
for (int b = 0; b < NUM_BODIES; b++) {
|
||||||
|
bodies[b].house = whole_sign_house(bodies[b].sign, ascendant_sign);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void astro_compute_natal_chart(const BirthData *birth, NatalChart *out) {
|
void astro_compute_natal_chart(const BirthData *birth, NatalChart *out) {
|
||||||
astro_time_t time = time_from_birth(birth);
|
astro_time_t time = time_from_birth(birth);
|
||||||
|
|
||||||
@@ -96,6 +110,8 @@ void astro_compute_natal_chart(const BirthData *birth, NatalChart *out) {
|
|||||||
for (int house = 0; house < 12; house++) {
|
for (int house = 0; house < 12; house++) {
|
||||||
out->houses[house] = (ZodiacSign)((ascendant_sign + house) % 12);
|
out->houses[house] = (ZodiacSign)((ascendant_sign + house) % 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assign_houses(out->bodies, out->houses[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static MoonPhaseName moon_phase_from_angle(double angle_deg) {
|
static MoonPhaseName moon_phase_from_angle(double angle_deg) {
|
||||||
@@ -129,6 +145,7 @@ void astro_compute_daily_transits(time_t utc_moment, const NatalChart *natal,
|
|||||||
astro_time_t time = time_from_unix(utc_moment);
|
astro_time_t time = time_from_unix(utc_moment);
|
||||||
|
|
||||||
compute_body_positions(time, out->bodies);
|
compute_body_positions(time, out->bodies);
|
||||||
|
assign_houses(out->bodies, natal->houses[0]);
|
||||||
|
|
||||||
astro_angle_result_t phase = Astronomy_MoonPhase(time);
|
astro_angle_result_t phase = Astronomy_MoonPhase(time);
|
||||||
out->moon_phase = moon_phase_from_angle(phase.angle);
|
out->moon_phase = moon_phase_from_angle(phase.angle);
|
||||||
|
|||||||
@@ -60,6 +60,11 @@ typedef struct {
|
|||||||
double ecliptic_longitude; /* geocentric, degrees, [0, 360) */
|
double ecliptic_longitude; /* geocentric, degrees, [0, 360) */
|
||||||
ZodiacSign sign;
|
ZodiacSign sign;
|
||||||
double degree_in_sign; /* [0, 30) */
|
double degree_in_sign; /* [0, 30) */
|
||||||
|
int house; /* 1-12, whole-sign, always relative to the natal Ascendant -
|
||||||
|
* for DailyTransits.bodies this places today's transiting
|
||||||
|
* planets in the natal chart's own houses (the standard,
|
||||||
|
* personalized way to read transits), not a fresh "houses
|
||||||
|
* for right now" chart. */
|
||||||
} PlanetPosition;
|
} PlanetPosition;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ void reading_generate(const char *tarot_seed, const BirthData *birth,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void print_position_deg(FILE *out, const PlanetPosition *p) {
|
static void print_position_deg(FILE *out, const PlanetPosition *p) {
|
||||||
fprintf(out, "%5.1f%s %s", p->degree_in_sign, "\xc2\xb0", astro_sign_name(p->sign));
|
fprintf(out, "%5.1f%s %-11s (%s %d)", p->degree_in_sign, "\xc2\xb0",
|
||||||
|
astro_sign_name(p->sign), ui("ui.house", "House"), p->house);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reading_print_text(const DailyReading *r, FILE *out) {
|
void reading_print_text(const DailyReading *r, FILE *out) {
|
||||||
@@ -68,8 +69,8 @@ void reading_print_text(const DailyReading *r, FILE *out) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void print_body_row_html(FILE *out, const char *label, const PlanetPosition *p) {
|
static void print_body_row_html(FILE *out, const char *label, const PlanetPosition *p) {
|
||||||
fprintf(out, "<tr><td>%s</td><td>%.1f° %s</td></tr>\n", label,
|
fprintf(out, "<tr><td>%s</td><td>%.1f° %s</td><td>%s %d</td></tr>\n", label,
|
||||||
p->degree_in_sign, astro_sign_name(p->sign));
|
p->degree_in_sign, astro_sign_name(p->sign), ui("ui.house", "House"), p->house);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reading_print_html(const DailyReading *r, FILE *out) {
|
void reading_print_html(const DailyReading *r, FILE *out) {
|
||||||
|
|||||||
@@ -52,6 +52,14 @@ static void test_natal_sun_sign(void) {
|
|||||||
astro_compute_natal_chart(&birth, &chart);
|
astro_compute_natal_chart(&birth, &chart);
|
||||||
|
|
||||||
assert(chart.bodies[PLANET_SUN].sign == SIGN_GEMINI);
|
assert(chart.bodies[PLANET_SUN].sign == SIGN_GEMINI);
|
||||||
|
|
||||||
|
/* Whole-sign house invariant: body.house (1-12) must name the house
|
||||||
|
* whose sign (chart.houses[house-1]) is that same body's sign. */
|
||||||
|
for (int b = 0; b < NUM_BODIES; b++) {
|
||||||
|
assert(chart.bodies[b].house >= 1 && chart.bodies[b].house <= 12);
|
||||||
|
assert(chart.houses[chart.bodies[b].house - 1] == chart.bodies[b].sign);
|
||||||
|
}
|
||||||
|
|
||||||
printf("PASS test_natal_sun_sign\n");
|
printf("PASS test_natal_sun_sign\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +82,12 @@ static void test_reading_generate_smoke(void) {
|
|||||||
for (int b = 0; b < NUM_BODIES; b++) {
|
for (int b = 0; b < NUM_BODIES; b++) {
|
||||||
assert(r1.natal.bodies[b].ecliptic_longitude == r2.natal.bodies[b].ecliptic_longitude);
|
assert(r1.natal.bodies[b].ecliptic_longitude == r2.natal.bodies[b].ecliptic_longitude);
|
||||||
assert(r1.transits.bodies[b].ecliptic_longitude == r2.transits.bodies[b].ecliptic_longitude);
|
assert(r1.transits.bodies[b].ecliptic_longitude == r2.transits.bodies[b].ecliptic_longitude);
|
||||||
|
|
||||||
|
/* Transiting planets' houses are read against the natal chart's
|
||||||
|
* houses, not a fresh chart for "now" - same invariant as the natal
|
||||||
|
* bodies, but checked against r1.natal.houses. */
|
||||||
|
assert(r1.transits.bodies[b].house >= 1 && r1.transits.bodies[b].house <= 12);
|
||||||
|
assert(r1.natal.houses[r1.transits.bodies[b].house - 1] == r1.transits.bodies[b].sign);
|
||||||
}
|
}
|
||||||
assert(r1.transits.aspect_count == r2.transits.aspect_count);
|
assert(r1.transits.aspect_count == r2.transits.aspect_count);
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user