# Your LLM Skill can't do astronomy: why packaged divination Skills compute the wrong answer

> Source: <https://dev.to/shanni/your-llm-skill-cant-do-astronomy-why-packaged-divination-skills-compute-the-wrong-answer-47nl>
> Published: 2026-08-17 07:01:19+00:00

There's been a wave of Chinese-divination Skills on GitHub this year. The biggest BaZi (Four Pillars astrology) one has ~2.5k stars — interactive chart casting against nine classical texts, plus MBTI mapping, compatibility, and elemental remedies. Another has ~800 stars and leads with engineered hallucination prevention: fixed casting steps, a structured knowledge base, external scripts. Below those: a ~450-star everything-bundle covering a dozen systems, a ~330-star one that converts chart output into AI-readable structured prompts and ships an API and an MCP server, and a ~270-star offline runtime that computes locally and exposes results to the model.

I've spent a year writing a BaZi engine. I've read through these projects, and to be fair: there are people doing serious work here. The one that treats anti-hallucination as goal #1 has the right instinct. The one that pushes computation into a local runtime has the right instinct too.

But almost all of them are inaccurate in a place nobody looks — not in the interpretation, in the arithmetic that happens before it.

That's backwards from how people assume this works. The casting step looks like the easy mechanical part and interpretation looks like the hard part. It's the reverse. Interpretation has no ground truth, so anything reads as plausible. Casting has exactly one right answer — and most implementations get it wrong.

Everything below generalizes past this domain. The pattern is: an LLM asked to do a lookup or a calculation will produce something that looks like the answer, and if your domain has no error signal, you will never find out.

Here's a run of my engine. Shanghai, February 3–5 1990, only the clock time changes:

```
1990-02-03 23:00 → 己巳 丁丑 己亥 乙亥
1990-02-04 10:00 → 己巳 丁丑 庚子 辛巳
1990-02-04 12:00 → 庚午 戊寅 庚子 壬午
1990-02-04 14:00 → 庚午 戊寅 庚子 癸未
1990-02-05 01:00 → 庚午 戊寅 辛丑 戊子
```

Look at the three rows for Feb 4. At 10:00 the first two pillars are 己巳 丁丑. At 12:00 they're 庚午 戊寅. The year and month pillars both changed in the middle of a single day.

Because the BaZi year doesn't turn at midnight on Jan 1, or at Lunar New Year. It turns at Lichun — the solar term that begins when the sun reaches 315° of ecliptic longitude. That's an instant, precise to the minute, different every year. Two people born the same calendar day, one in the morning and one after lunch, get different year and month pillars. Not slightly different — 4 of the 8 characters differ.

So: how does a Skill know what time Lichun occurred in 1990?

There are exactly two ways. Either it ships a table of every solar term for every year to minute precision (1900–2100 × 24 terms ≈ 48,000 timestamps), or it computes the astronomy at runtime. Most of these Skills do neither — they encode solar term dates in a Markdown knowledge base. Day granularity. Which means for everyone born on the boundary day, the year pillar is either entirely right or entirely wrong depending on which half the model guessed.

This is not an interpretive style difference. A quarter of the chart is simply incorrect.

Same wall-clock time, same national timezone, two cities. June 15 1992, 07:10 Beijing time:

```
Shanghai  121.5°E : solar correction   +6 min → 壬申 丙午 壬戌 甲辰
Ürümqi     87.6°E : solar correction −130 min → 壬申 丙午 壬戌 壬寅
```

Different hour pillar. 甲辰 vs 壬寅 — both characters change.

China spans roughly four geographic time zones (73°E to 135°E) and runs on one official clock. BaZi uses the sun where you were born. At 07:10 on the Ürümqi clock, local solar time is barely past 05:00 — the sun is only just up. That's the 寅 hour, not the 辰 hour. Two full branches apart.

Computing this correction needs three quantities: the birth longitude, the UTC offset in effect at that place on that date, and the equation of time — the ±15-minute daily discrepancy between true and mean solar time caused by Earth's elliptical orbit and axial tilt. That last one is a trigonometric series, evaluated per day-of-year.

Can a model do this in-context? Realistically, no. Asking it to evaluate the equation of time is asking for a plausible number. Asking it to recall longitude fails differently: the user says "I was born in Ürümqi," the model supplies an approximate longitude, and one degree of error is four minutes — which near an hour boundary is enough to flip the pillar.

This is the one I find most instructive.

```
1988-07-01 08:00 Shanghai: total correction −58 min
  decomposed: DST −60, longitude +6, equation of time −4
```

8 AM on the clock in Shanghai in July 1988 was really 7:02, because China observed daylight saving time from 1986 to 1991. Almost nobody remembers this. But people born in those six years are in their thirties now — squarely the demographic that goes looking for a reading. Their birth certificates record a clock that had been moved forward an hour. An hour is enough to cross an entire hour-branch, and at the right time of night, the day pillar too.

It gets worse at the transition itself. On the spring-forward date, local 02:00 jumps straight to 03:00 — that hour never existed locally. If a birth record says 1988-04-17 02:30, that timestamp is not a real instant.

My own engine got this wrong first. The original implementation reverse-solved the UTC instant by fixed-point iteration, which doesn't converge for a nonexistent input — but it returned a result anyway. Consequence: 02:59 and 03:00 mapped to the same real instant while producing different hour pillars, and their reported corrections differed by a full 60 minutes. It's now pinned by a test that walks the gap across all six Chinese DST years, plus the minutes on either side of it.

I bring this up not to show off the fix but to make the central point: none of these errors throw. The system quietly hands you eight tidy characters. Nothing about the output looks degraded. Feed that chart to any competent LLM and it will fluently explain what those characters mean about your life — and the better the prose, the more thoroughly wrong the result.

Because this domain has no verification loop.

You enter a birthday, you get a paragraph, and it feels like it fits. That feeling has three sources: the Barnum effect (sufficiently general descriptions fit everyone), the model's genuine fluency (it was trained to generate text you find apt), and your own cooperation (people unconsciously supply confirming evidence for a reading they've already heard).

Together those are more than enough to make a system with a completely incorrect chart read as accurate.

This is the uncomfortable part of working in this space. In most domains a miscalculation produces feedback — the build breaks, the ledger doesn't balance, the bridge falls down. Here there's nothing. The user won't know, the model won't know, and the Skill author won't know either. Unless someone writes a test.

So there's really one question I use to judge whether a divination project is serious: does it have failing test cases for the chart casting? Not tests that assert it runs — tests that assert this input must be rejected, or must return A and never B. The anti-hallucination project mentioned earlier is pointed the right way with its external scripts and fixed steps; so is the local-runtime one. But between "pointed the right way" and "computes correctly" sit three separate mountains: the solar term ephemeris, the equation of time, and the historical timezone database.

To be clear, Skills as a format aren't the problem. The task split is.

Chart casting is pure computation. Given an instant and a place, the eight characters are uniquely determined; there is zero interpretive latitude. That belongs in code: a pure function, unit tested, identical every run.

Interpretation is language. The same chart can be read gently or bluntly, weighted toward career or temperament. Models are genuinely good at that — better than I am.

The failure is handing the first job to the model too. An LLM's reliability at table lookup and its reliability at prose are not remotely the same number. Ask it to recall 48,000 solar term timestamps and it will hand you something that looks a great deal like one.

My rule, which I'd apply to any LLM product that must not invent things:

The engine decides what is said. The model decides only how to say it.

Eight characters, elemental strengths, favorable elements, luck cycles — all computed in TypeScript, unit tested, with the constants published on a public method page. The model receives a block of facts and one directive: cite only what's given. It's a translator with a persona, not an oracle.

There's a second benefit that's easy to miss: once the engine owns the facts, it can also own the uncertainty. When a user doesn't know their birth hour, I don't pick a default and hope. There are exactly 12 possibilities, and computeChart is a pure function, so I compute all 12 and intersect them — only conclusions that hold in every candidate reach the prompt; the rest are left blank, explicitly. When a corrected time lands within a few minutes of an hour boundary, I cast the chart on the other side too and show precisely what changes and what doesn't.

A stated hole beats a silent one. Leave the slot empty and the model backfills it, convincingly. Mark it and instruct it, and the model routes around it.

If you're using one of these Skills, three questions you can verify yourself:

If it can't answer one of the three, the chart has a meaningful chance of being wrong. It will read exactly as convincing either way.

If you're writing one, the advice is a single line: pull the casting out into code, and give it tests that can fail. That last clause matters. I once deleted a validator I'd shipped months earlier because a code review revealed both of its loops asked whether a set contained elements drawn from that same set — always true, never able to fire. It had caught zero violations and structurally could not catch any. Meanwhile it appeared in every architecture discussion as "we validate that," so everyone stopped thinking about it. A guardrail that cannot fire is worse than no guardrail, and an assertion that has never gone red is a hypothesis, not a gate.

Get the arithmetic right first. Then hand the prose to the model — it really is better at that part than you are.

Every chart above is a live run of the engine behind [Auspice Oracle](https://auspiceoracle.com). The handling of solar term instants, true solar time, and historical DST is documented on the [method page](https://auspiceoracle.com/en/method).
