Report sign/house in significant transits; back up user.properties outside dist/
This commit is contained in:
@@ -3,6 +3,11 @@
|
||||
/dist
|
||||
/build
|
||||
|
||||
# Durable backup of the same real birth data, outside dist/ so a dist/
|
||||
# wipe doesn't lose it — see the Makefile's `scripts` target. Never
|
||||
# commit this either.
|
||||
/engine/scripts/user.properties.local
|
||||
|
||||
*.o
|
||||
core
|
||||
core.*
|
||||
|
||||
@@ -45,14 +45,20 @@ dist/deck-engine --seed "2026-07-03" \
|
||||
```
|
||||
|
||||
`dist/user.properties` is seeded once from `engine/scripts/user.properties.template`
|
||||
with placeholder values (`YOUR_BIRTH_DATE_HERE`, etc.) and is **never
|
||||
overwritten by later builds** — `run-engine.sh`/`run-interpreter.sh`
|
||||
refuse to run (with a clear error) until the placeholders are replaced
|
||||
with real values. `dist/run-interpreter.sh` is the equivalent daily-use
|
||||
wrapper for the interpreter: it runs `run-engine.sh`'s same OS-clock
|
||||
seed/date and `user.properties` birth data through `deck-engine --format
|
||||
json`, piped straight into `dist/interpreter-cli` — see "Interpretation"
|
||||
below.
|
||||
with placeholder values (`YOUR_BIRTH_DATE_HERE`, etc.) — `run-engine.sh`/
|
||||
`run-interpreter.sh` refuse to run (with a clear error) until the
|
||||
placeholders are replaced with real values. Because `dist/` itself is
|
||||
disposable (`make clean`, or just deleting the directory, wipes it), the
|
||||
`scripts` Makefile target also keeps a durable backup outside `dist/`:
|
||||
once `dist/user.properties` looks filled in (no leftover `YOUR_...`), every
|
||||
build copies it out to `engine/scripts/user.properties.local`
|
||||
(gitignored); if `dist/user.properties` is ever missing when a build
|
||||
runs, it's restored from that backup instead of being reseeded from the
|
||||
placeholder template. `dist/run-interpreter.sh` is the equivalent
|
||||
daily-use wrapper for the interpreter: it runs `run-engine.sh`'s same
|
||||
OS-clock seed/date and `user.properties` birth data through `deck-engine
|
||||
--format json`, piped straight into `dist/interpreter-cli` — see
|
||||
"Interpretation" below.
|
||||
|
||||
Run a single smoke test by editing `engine/tests/smoke_test.c`'s `main()`
|
||||
temporarily, or just read its assertions — there's no test filter flag,
|
||||
@@ -241,12 +247,19 @@ between the two binaries (see the next bullet).
|
||||
Unlike the core engine, it uses `malloc`; `interpreter-cli` was never
|
||||
meant to run on the watch itself.
|
||||
- **`reading_io.c`** walks the parsed JSON down to `"transits"."aspects"`
|
||||
and fills a `DailyReading` with just that (everything else is left
|
||||
zeroed - `interpret_daily_reading` doesn't read `natal`/`spread`
|
||||
either). An aspect naming a planet/aspect-type slug it doesn't
|
||||
recognize, or a document missing `"transits"."aspects"` entirely (as
|
||||
opposed to a present-but-empty array, which is a valid "no aspects
|
||||
today"), makes the whole load fail rather than silently dropping data.
|
||||
and fills a `DailyReading` with those (used for scoring), plus
|
||||
`"natal"."bodies"`/`"transits"."bodies"` (each body's sign + whole-sign
|
||||
house, via `parse_bodies()` — used only for the sign/house context
|
||||
`main.c` prints alongside each significant event, never for scoring).
|
||||
`"spread"` is left zeroed entirely - nothing reads it. An aspect naming
|
||||
a planet/aspect-type slug it doesn't recognize, or a document missing
|
||||
`"transits"."aspects"` entirely (as opposed to a present-but-empty
|
||||
array, which is a valid "no aspects today"), makes the whole load fail;
|
||||
by contrast an unrecognized body slug or missing `house` inside
|
||||
`"bodies"` is silently skipped (that body's `house` stays `0`, an
|
||||
invalid whole-sign house number used as "no data" - `main.c` checks for
|
||||
it before printing sign/house), since that's supplementary display
|
||||
context rather than something scoring depends on.
|
||||
- **Deliberately header-only dependency on the engine, throughout**:
|
||||
`significance.h`/`reading_io.h` `#include engine/src/reading.h` for the
|
||||
struct/enum *definitions*, but the root `Makefile` never compiles or
|
||||
@@ -254,8 +267,8 @@ between the two binaries (see the next bullet).
|
||||
interpreter's binary or tests — every
|
||||
test in `interpreter/tests/` runs against hand-built JSON text or
|
||||
hand-built `DailyReading` values, with no real ephemeris/tarot-draw
|
||||
call involved anywhere. Consequently `reading_io.c` (planet/aspect
|
||||
slugs) and `main.c` (planet/aspect display names) each duplicate a
|
||||
call involved anywhere. Consequently `reading_io.c` (planet/aspect/sign
|
||||
slugs) and `main.c` (planet/aspect/sign display names) each duplicate a
|
||||
small local table rather than linking `astro.c` to reuse its own -
|
||||
keep those in sync if the engine's slugs/names ever change.
|
||||
- Only `Aspect`s compete for the top 5 so far (`SignificantItemKind` is
|
||||
|
||||
@@ -17,6 +17,11 @@ ENGINE_I18N_DIR := engine/i18n
|
||||
ENGINE_SCRIPTS_DIR := engine/scripts
|
||||
RES_IMG_DIR := res/img
|
||||
|
||||
# Durable copy of the user's real birth data, outside dist/ (which is
|
||||
# disposable - `make clean`, or just deleting the directory, wipes it).
|
||||
# Gitignored: see .gitignore.
|
||||
USER_PROPS_BACKUP := $(ENGINE_SCRIPTS_DIR)/user.properties.local
|
||||
|
||||
# Engine logic shared by the CLI and the test binary (no main()).
|
||||
ENGINE_SRCS := $(ENGINE_SRC_DIR)/rng.c $(ENGINE_SRC_DIR)/tarot.c $(ENGINE_SRC_DIR)/tarot_data.c \
|
||||
$(ENGINE_SRC_DIR)/astro.c $(ENGINE_SRC_DIR)/reading.c $(ENGINE_SRC_DIR)/i18n.c \
|
||||
@@ -79,16 +84,27 @@ i18n:
|
||||
cp $(ENGINE_I18N_DIR)/*.lang $(DIST_I18N_DIR)/
|
||||
|
||||
# run-engine.sh/run-interpreter.sh are refreshed every build.
|
||||
# user.properties is only seeded once (from the placeholder template) so
|
||||
# a rebuild never clobbers the user's own filled-in birth data.
|
||||
#
|
||||
# user.properties is backed up/restored across dist/ wipes rather than
|
||||
# just "seeded once": if dist/user.properties exists and looks filled in
|
||||
# (no leftover YOUR_... placeholder), it's copied out to
|
||||
# USER_PROPS_BACKUP; otherwise, if that backup exists (e.g. dist/ was
|
||||
# just deleted), it's copied back in; otherwise (genuinely first run)
|
||||
# dist/user.properties is seeded from the placeholder template, same as
|
||||
# before.
|
||||
scripts:
|
||||
@mkdir -p $(DIST_DIR)
|
||||
cp $(ENGINE_SCRIPTS_DIR)/run-engine.sh $(DIST_DIR)/run-engine.sh
|
||||
chmod +x $(DIST_DIR)/run-engine.sh
|
||||
cp $(ENGINE_SCRIPTS_DIR)/run-interpreter.sh $(DIST_DIR)/run-interpreter.sh
|
||||
chmod +x $(DIST_DIR)/run-interpreter.sh
|
||||
test -f $(DIST_DIR)/user.properties || \
|
||||
cp $(ENGINE_SCRIPTS_DIR)/user.properties.template $(DIST_DIR)/user.properties
|
||||
@if [ -f $(DIST_DIR)/user.properties ] && ! grep -q 'YOUR_' $(DIST_DIR)/user.properties; then \
|
||||
cp $(DIST_DIR)/user.properties $(USER_PROPS_BACKUP); \
|
||||
elif [ -f $(USER_PROPS_BACKUP) ]; then \
|
||||
cp $(USER_PROPS_BACKUP) $(DIST_DIR)/user.properties; \
|
||||
elif [ ! -f $(DIST_DIR)/user.properties ]; then \
|
||||
cp $(ENGINE_SCRIPTS_DIR)/user.properties.template $(DIST_DIR)/user.properties; \
|
||||
fi
|
||||
|
||||
test: $(ENGINE_TEST_BINARY) $(SIGNIFICANCE_TEST_BIN) $(JSON_TEST_BIN)
|
||||
./$(ENGINE_TEST_BINARY)
|
||||
|
||||
@@ -36,8 +36,11 @@ know before changing the engine. This document is the visual overview.
|
||||
- **`dist/`** is generated by the single root `Makefile`: the
|
||||
`deck-engine` and `interpreter-cli` binaries, a copy of the card art
|
||||
under `img/`, a copy of the translation files under `i18n/`,
|
||||
`run-engine.sh`/`run-interpreter.sh`, and a `user.properties` template
|
||||
that's seeded once and never overwritten by later builds.
|
||||
`run-engine.sh`/`run-interpreter.sh`, and `user.properties`. Since
|
||||
`dist/` itself is disposable, `user.properties` also gets backed up to
|
||||
`engine/scripts/user.properties.local` (gitignored) once it's filled
|
||||
in, and restored from there if `dist/` is ever wiped — see
|
||||
`CLAUDE.md`'s "Build & run".
|
||||
- **The Pebble watchapp does not exist yet.** When it's built, it will
|
||||
call `reading_generate()` directly and walk the returned struct to lay
|
||||
out its own screens — it has no reason to touch `reading_print_*`.
|
||||
|
||||
+22
-14
@@ -55,11 +55,15 @@ use:
|
||||
```
|
||||
|
||||
The file is seeded once from `engine/scripts/user.properties.template`
|
||||
with placeholder values (`YOUR_BIRTH_DATE_HERE`, etc.) and is **never
|
||||
overwritten by later builds**. `run-engine.sh` checks every birth field
|
||||
for an empty value or a leftover `YOUR_` placeholder and refuses to run
|
||||
with a clear error until the file is filled in; `lang` has no such
|
||||
check since it always has a usable default (`en`).
|
||||
with placeholder values (`YOUR_BIRTH_DATE_HERE`, etc.). `run-engine.sh`
|
||||
checks every birth field for an empty value or a leftover `YOUR_`
|
||||
placeholder and refuses to run with a clear error until the file is
|
||||
filled in; `lang` has no such check since it always has a usable
|
||||
default (`en`). Since `dist/` itself can be wiped (`make clean`, or
|
||||
deleting the directory), the build also keeps a durable backup at
|
||||
`engine/scripts/user.properties.local` (gitignored) once the file
|
||||
looks filled in, and restores from it if `dist/user.properties` is
|
||||
ever missing — see `CLAUDE.md`'s "Build & run".
|
||||
- The optional second argument overrides `lang=` for a single run without
|
||||
editing the file, e.g. `dist/run-engine.sh html de`.
|
||||
- `dist/run-interpreter.sh` (no arguments) is the equivalent wrapper for
|
||||
@@ -236,22 +240,26 @@ dist/run-interpreter.sh
|
||||
```
|
||||
|
||||
- **Input**: a file path argument, or stdin if no argument (or `-`) is
|
||||
given. Only `"transits"."aspects"` is read — `natal` and `spread` may
|
||||
be present (and are ignored) or omitted entirely, except that
|
||||
`"transits"."aspects"` itself must be present (an empty array is valid
|
||||
and means "no notable transits today"; a missing key is treated as
|
||||
invalid input).
|
||||
given. `"transits"."aspects"` is required (an empty array is valid and
|
||||
means "no notable transits today"; a missing key is treated as invalid
|
||||
input) and drives scoring. `"natal"."bodies"`/`"transits"."bodies"` are
|
||||
read too, for the sign/house context printed alongside each event below
|
||||
(not for scoring) — an entry naming an unrecognized body, or a missing
|
||||
`house`, is silently skipped rather than failing the load (see
|
||||
`parse_bodies()` in `reading_io.c`); `spread` is always ignored.
|
||||
- **Output**: a plain-text report — the day's overall significance level
|
||||
(`Quiet`/`Notable`/`Significant`/`Major`), a "worth a deeper Celtic
|
||||
Cross look" line on `Major` days only, and up to 5 of today's aspects
|
||||
ranked by score:
|
||||
ranked by score, each naming the sign and natal house the transiting
|
||||
planet currently occupies and the sign and house of the natal planet
|
||||
it's aspecting:
|
||||
|
||||
```
|
||||
Day significance: Significant
|
||||
(a major transit today - worth a deeper Celtic Cross look)
|
||||
|
||||
Top 3 significant transits:
|
||||
1. Transiting Saturn Square natal Sun (orb 0.5°, score 8.10)
|
||||
2. Transiting Pluto Opposition natal Moon (orb 0.2°, score 7.92)
|
||||
3. Transiting Jupiter Trine natal Venus (orb 2.1°, score 3.40)
|
||||
1. Transiting Saturn in Aries (house 2) Square natal Sun in Taurus (house 3) (orb 0.5°, score 8.10)
|
||||
2. Transiting Pluto in Aquarius (house 12) Opposition natal Moon in Capricorn (house 11) (orb 0.2°, score 7.92)
|
||||
3. Transiting Jupiter in Leo (house 5) Trine natal Venus in Aries (house 2) (orb 2.1°, score 3.40)
|
||||
```
|
||||
|
||||
+23
-4
@@ -57,6 +57,22 @@ static const char *aspect_display_name(AspectType type) {
|
||||
return names[type];
|
||||
}
|
||||
|
||||
static const char *sign_display_name(ZodiacSign sign) {
|
||||
static const char *const names[12] = {
|
||||
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
|
||||
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces",
|
||||
};
|
||||
return names[sign];
|
||||
}
|
||||
|
||||
/* pos->house is 0 when reading_load_json had no sign/house data for this
|
||||
* body (see parse_bodies() in reading_io.c) - a valid whole-sign house
|
||||
* is always 1-12, so this is a safe "unknown, say nothing" sentinel. */
|
||||
static void print_body_in_sign(const PlanetPosition *pos) {
|
||||
if (pos->house < 1 || pos->house > 12) return;
|
||||
printf(" in %s (house %d)", sign_display_name(pos->sign), pos->house);
|
||||
}
|
||||
|
||||
static const char *day_level_name(DaySignificance level) {
|
||||
switch (level) {
|
||||
case DAY_QUIET: return "Quiet";
|
||||
@@ -114,10 +130,13 @@ int main(int argc, char **argv) {
|
||||
interp.top_item_count == 1 ? "" : "s");
|
||||
for (int i = 0; i < interp.top_item_count; i++) {
|
||||
const SignificantItem *item = &interp.top_items[i];
|
||||
printf(" %d. Transiting %s %s natal %s (orb %.1f\xc2\xb0, score %.2f)\n", i + 1,
|
||||
body_display_name(item->aspect.transiting_planet),
|
||||
aspect_display_name(item->aspect.type),
|
||||
body_display_name(item->aspect.natal_planet), item->aspect.orb, item->score);
|
||||
const Aspect *a = &item->aspect;
|
||||
|
||||
printf(" %d. Transiting %s", i + 1, body_display_name(a->transiting_planet));
|
||||
print_body_in_sign(&reading.transits.bodies[a->transiting_planet]);
|
||||
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);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ static const char *const k_aspect_slug[5] = {
|
||||
"conjunction", "sextile", "square", "trine", "opposition",
|
||||
};
|
||||
|
||||
static const char *const k_sign_slug[12] = {
|
||||
"aries", "taurus", "gemini", "cancer", "leo", "virgo",
|
||||
"libra", "scorpio", "sagittarius", "capricorn", "aquarius", "pisces",
|
||||
};
|
||||
|
||||
static bool body_from_slug(const char *slug, Body *out) {
|
||||
for (int i = 0; i < NUM_BODIES; i++) {
|
||||
if (strcmp(slug, k_body_slug[i]) == 0) {
|
||||
@@ -27,6 +32,41 @@ static bool body_from_slug(const char *slug, Body *out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool sign_from_slug(const char *slug, ZodiacSign *out) {
|
||||
for (int i = 0; i < 12; i++) {
|
||||
if (strcmp(slug, k_sign_slug[i]) == 0) {
|
||||
*out = (ZodiacSign)i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Fills out[NUM_BODIES] from a "bodies" JSON array, the shape shared by
|
||||
* both "natal" and "transits" (see reading_print_json in engine/src/
|
||||
* reading.c). Used only for sign/house context in significant-event
|
||||
* reporting, not for scoring, so this is deliberately lenient: an entry
|
||||
* with an unrecognized/missing "body" slug is skipped, and a missing
|
||||
* "house" leaves that body's house at 0 - not a valid whole-sign house
|
||||
* number (always 1-12), so callers use house == 0 as "no position data
|
||||
* available" rather than this failing the whole load. */
|
||||
static void parse_bodies(const JsonValue *bodies, PlanetPosition out[NUM_BODIES]) {
|
||||
int count = json_array_count(bodies);
|
||||
for (int i = 0; i < count; i++) {
|
||||
const JsonValue *item = json_array_get(bodies, i);
|
||||
Body body;
|
||||
if (!body_from_slug(json_as_string(json_object_get(item, "body")), &body)) continue;
|
||||
|
||||
ZodiacSign sign;
|
||||
if (sign_from_slug(json_as_string(json_object_get(item, "sign")), &sign)) {
|
||||
out[body].sign = sign;
|
||||
}
|
||||
out[body].degree_in_sign = json_as_number(json_object_get(item, "degree_in_sign"));
|
||||
out[body].ecliptic_longitude = json_as_number(json_object_get(item, "ecliptic_longitude"));
|
||||
out[body].house = (int)json_as_number(json_object_get(item, "house"));
|
||||
}
|
||||
}
|
||||
|
||||
static bool aspect_type_from_slug(const char *slug, AspectType *out) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (strcmp(slug, k_aspect_slug[i]) == 0) {
|
||||
@@ -78,6 +118,10 @@ bool reading_load_json(const char *text, size_t length, DailyReading *out) {
|
||||
}
|
||||
out->transits.aspect_count = filled;
|
||||
|
||||
const JsonValue *natal = json_object_get(root, "natal");
|
||||
parse_bodies(natal ? json_object_get(natal, "bodies") : NULL, out->natal.bodies);
|
||||
parse_bodies(json_object_get(transits, "bodies"), out->transits.bodies);
|
||||
|
||||
json_free(root);
|
||||
return ok;
|
||||
}
|
||||
|
||||
@@ -10,14 +10,18 @@
|
||||
|
||||
/* Parses deck-engine's `--format json` output (text, null-terminated at
|
||||
* text[length]) and fills *out with just enough of a DailyReading for
|
||||
* interpret_daily_reading() to work on: out->transits.aspects[]/
|
||||
* aspect_count. out->natal and out->spread are left zeroed - nothing
|
||||
* here reads them (see significance.c), so there's no reason to parse
|
||||
* the rest of the document yet.
|
||||
* interpret_daily_reading() to work on and for reporting sign/house
|
||||
* context around each significant event: out->transits.aspects[]/
|
||||
* aspect_count (used for scoring - see significance.c), and
|
||||
* out->natal.bodies[]/out->transits.bodies[] (sign + house per body,
|
||||
* used only for display). out->natal.ascendant_longitude/houses[] and
|
||||
* out->spread are left zeroed - nothing reads them yet.
|
||||
*
|
||||
* Returns false on malformed JSON, or if "transits"."aspects" isn't
|
||||
* present as an array (an empty array is fine - that's a real "no
|
||||
* aspects today" reading, not an error). */
|
||||
* aspects today" reading, not an error). A missing/unrecognized entry
|
||||
* in "natal"."bodies"/"transits"."bodies" is not an error - see
|
||||
* parse_bodies() in reading_io.c. */
|
||||
bool reading_load_json(const char *text, size_t length, DailyReading *out);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -41,14 +41,16 @@ static void test_json_parse_rejects_malformed(void) {
|
||||
}
|
||||
|
||||
/* A trimmed-but-structurally-faithful fixture matching deck-engine's real
|
||||
* --format json shape: natal/spread sections are present with decoy
|
||||
* content the loader must skip over without understanding their schema,
|
||||
* to prove it navigates by key path rather than assuming any position. */
|
||||
* --format json shape: "spread" is present with decoy content the loader
|
||||
* must skip over without understanding its schema, to prove it navigates
|
||||
* by key path rather than assuming any position. "natal"/"transits"
|
||||
* bodies are real (if partial) - reading_load_json now parses sign/house
|
||||
* from them for display, alongside the aspects used for scoring. */
|
||||
static const char *k_fixture =
|
||||
"{"
|
||||
" \"natal\": {\"bodies\": [{\"body\": \"sun\", \"sign\": \"taurus\"}], \"houses\": []},"
|
||||
" \"natal\": {\"bodies\": [{\"body\": \"sun\", \"sign\": \"taurus\", \"house\": 3}], \"houses\": []},"
|
||||
" \"transits\": {"
|
||||
" \"bodies\": [{\"body\": \"sun\", \"sign\": \"cancer\"}],"
|
||||
" \"bodies\": [{\"body\": \"sun\", \"sign\": \"cancer\", \"house\": 5}],"
|
||||
" \"moon_phase\": \"full\","
|
||||
" \"aspects\": ["
|
||||
" {\"transiting_planet\": \"pluto\", \"natal_planet\": \"moon\", \"type\": \"opposition\", \"orb\": 0.2},"
|
||||
@@ -75,6 +77,37 @@ static void test_reading_load_json_extracts_aspects(void) {
|
||||
printf("PASS test_reading_load_json_extracts_aspects\n");
|
||||
}
|
||||
|
||||
static void test_reading_load_json_extracts_body_sign_and_house(void) {
|
||||
DailyReading reading;
|
||||
bool ok = reading_load_json(k_fixture, strlen(k_fixture), &reading);
|
||||
|
||||
assert(ok);
|
||||
assert(reading.natal.bodies[PLANET_SUN].sign == SIGN_TAURUS);
|
||||
assert(reading.natal.bodies[PLANET_SUN].house == 3);
|
||||
assert(reading.transits.bodies[PLANET_SUN].sign == SIGN_CANCER);
|
||||
assert(reading.transits.bodies[PLANET_SUN].house == 5);
|
||||
|
||||
/* Bodies missing from the fixture (e.g. the Moon) get the "no data"
|
||||
* sentinel rather than a garbage house number. */
|
||||
assert(reading.natal.bodies[PLANET_MOON].house == 0);
|
||||
assert(reading.transits.bodies[PLANET_MOON].house == 0);
|
||||
|
||||
printf("PASS test_reading_load_json_extracts_body_sign_and_house\n");
|
||||
}
|
||||
|
||||
static void test_reading_load_json_unknown_body_slug_is_skipped_not_fatal(void) {
|
||||
const char *text =
|
||||
"{\"natal\": {\"bodies\": [{\"body\": \"xenu\", \"sign\": \"taurus\", \"house\": 3}]},"
|
||||
" \"transits\": {\"aspects\": []}}";
|
||||
DailyReading reading;
|
||||
bool ok = reading_load_json(text, strlen(text), &reading);
|
||||
|
||||
assert(ok);
|
||||
assert(reading.transits.aspect_count == 0);
|
||||
|
||||
printf("PASS test_reading_load_json_unknown_body_slug_is_skipped_not_fatal\n");
|
||||
}
|
||||
|
||||
static void test_reading_load_json_empty_aspects_is_valid(void) {
|
||||
const char *text = "{\"transits\": {\"aspects\": []}}";
|
||||
DailyReading reading;
|
||||
@@ -113,6 +146,8 @@ int main(void) {
|
||||
test_json_parse_basic_shapes();
|
||||
test_json_parse_rejects_malformed();
|
||||
test_reading_load_json_extracts_aspects();
|
||||
test_reading_load_json_extracts_body_sign_and_house();
|
||||
test_reading_load_json_unknown_body_slug_is_skipped_not_fatal();
|
||||
test_reading_load_json_empty_aspects_is_valid();
|
||||
test_reading_load_json_rejects_missing_transits();
|
||||
test_reading_load_json_rejects_unknown_slug();
|
||||
|
||||
Reference in New Issue
Block a user