<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jingcheng-xie</title>
    <description>The latest articles on DEV Community by Jingcheng-xie (@jingcheng-xie).</description>
    <link>https://dev.to/jingcheng-xie</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4008891%2F359cefb4-f1ac-4057-bf03-7a4e9f93d6b6.png</url>
      <title>DEV Community: Jingcheng-xie</title>
      <link>https://dev.to/jingcheng-xie</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jingcheng-xie"/>
    <language>en</language>
    <item>
      <title>My tests were green. My code was wrong. The spec was 3,000 years old.</title>
      <dc:creator>Jingcheng-xie</dc:creator>
      <pubDate>Thu, 02 Jul 2026 13:06:49 +0000</pubDate>
      <link>https://dev.to/jingcheng-xie/my-tests-were-green-my-code-was-wrong-the-spec-was-3000-years-old-3264</link>
      <guid>https://dev.to/jingcheng-xie/my-tests-were-green-my-code-was-wrong-the-spec-was-3000-years-old-3264</guid>
      <description>&lt;p&gt;Most of the code we write can be checked against something. A tax calculator has the tax code. A JSON parser has a spec. A physics sim has reality. You write a test, you assert the expected value, and the "expected value" comes from somewhere authoritative.&lt;/p&gt;

&lt;p&gt;But what do you do when there is &lt;strong&gt;no authority&lt;/strong&gt;? When the "spec" is 3000 years old, written in classical Chinese, has multiple competing schools that flatly contradict each other, and the human experts you'd ask for the "right answer" disagree among themselves — and are sometimes just wrong?&lt;/p&gt;

&lt;p&gt;That was my problem. I set out to build a &lt;strong&gt;deterministic engine for I Ching (六爻 / Liù Yáo) hexagram casting&lt;/strong&gt; — the part that takes a moment in time and a coin toss and mechanically derives a fully annotated chart. Not the interpretation. Just the hard, mechanical facts: which hexagram, its palace, the stem-branch assignments, the "six relatives," the moving lines, the void days.&lt;/p&gt;

&lt;p&gt;And I hit the wall every developer in an under-specified domain hits: &lt;strong&gt;the self-validation paradox.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The self-validation paradox
&lt;/h2&gt;

&lt;p&gt;Here's the trap. I write the engine. Then I write tests for it. But &lt;em&gt;I&lt;/em&gt; am the one who decided what the engine should output — so my tests just encode the same assumptions my code does. If I misunderstood a rule, my code is wrong &lt;strong&gt;and&lt;/strong&gt; my test asserting that wrong behavior is green. The bug and its "proof of correctness" share a single brain: mine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   My understanding of the rules
            │
      ┌─────┴─────┐
      ▼           ▼
   the code    the test
      │           │
      └─────┬─────┘
            ▼
     both agree → green ✓   (even when both are wrong)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can't test your way out of a misconception using tests you wrote from the same misconception.&lt;/p&gt;

&lt;p&gt;The usual escape is "ask an expert." But for this domain that fails on every axis I care about as an engineer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not reproducible.&lt;/strong&gt; An expert's ruling is a one-off. Six months later I can't re-run it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not scalable.&lt;/strong&gt; There are 64 hexagrams × 6 possible moving lines × 60 day-pillars × … the combinatorial space is huge. No human is checking thousands of charts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not infallible.&lt;/strong&gt; Experts disagree, and any single one can be wrong. "Trust me" is not a test suite.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted correctness to be a &lt;strong&gt;reproducible engineering fact&lt;/strong&gt;, not an appeal to authority. So I borrowed a technique from compilers and numerical code: &lt;strong&gt;differential testing against an independent oracle.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Differential testing: diff against someone who solved it independently
&lt;/h2&gt;

&lt;p&gt;The idea is simple. If I can't trust my own answer key, I find a &lt;em&gt;second, completely independent implementation&lt;/em&gt; of the same rules and diff against it on a large sample of inputs. Any disagreement is a bug — in mine or in the reference — and either way it's something I must run down and reproduce.&lt;/p&gt;

&lt;p&gt;The key word is &lt;strong&gt;independent&lt;/strong&gt;. If I test one of my modules against another of my modules, they share my misconceptions and the diff proves nothing. The oracle has to come from a different author, a different codebase, ideally a different community that arrived at the rules on their own.&lt;/p&gt;

&lt;p&gt;For hexagram casting I picked &lt;a href="https://github.com/bopo/najia" rel="noopener noreferrer"&gt;&lt;code&gt;najia&lt;/code&gt;&lt;/a&gt; (MIT-licensed) as the primary oracle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;License is clean&lt;/strong&gt; — MIT, safe to depend on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's programmable&lt;/strong&gt; — I can drive it from a script over thousands of combinations, not click through a UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's community-validated&lt;/strong&gt; — other people already rely on it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then I stacked a &lt;em&gt;second&lt;/em&gt; independent source on top: &lt;a href="https://github.com/AdrienSterling/yigram-najia-rules" rel="noopener noreferrer"&gt;&lt;code&gt;yigram-najia-rules&lt;/code&gt;&lt;/a&gt; (also MIT, a machine-readable rule table). A field is only trusted when &lt;strong&gt;all three agree&lt;/strong&gt; — mine, najia, and yigram. Three sources sharply lowers the odds that "two implementations share the same bug."&lt;/p&gt;

&lt;p&gt;Here's the actual test. It samples 200 random hexagrams across random dates and compares &lt;strong&gt;every field&lt;/strong&gt; of the chart:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_differential_vs_najia&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;200 random hexagrams × random dates, field-by-field agreement with najia.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20260616&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# fixed seed → reproducible
&lt;/span&gt;    &lt;span class="n"&gt;mismatches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;  &lt;span class="c1"&gt;# the six lines
&lt;/span&gt;        &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_DATES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_najia_chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mark&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# Feed najia's OWN day-stem into my engine → isolate casting logic
&lt;/span&gt;        &lt;span class="c1"&gt;# from calendar logic, so this test compares ONLY the charting rules.
&lt;/span&gt;        &lt;span class="n"&gt;day_gan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GAN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lunar&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;day&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cast_chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day_gan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;checks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hexagram name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;palace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;palace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gong&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stem-branch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;najia&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaos&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get_najia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;six relatives&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;liuqin&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaos&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qin6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;six gods&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;liushen&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaos&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;god6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;self line&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shi_pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shiy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response line&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ying_pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shiy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;theirs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mine&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;theirs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;mismatches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: mine=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mine&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; najia=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;theirs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;mismatches&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;differential mismatch:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mismatches&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Two details worth stealing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fixed seed&lt;/strong&gt; (&lt;code&gt;random.Random(20260616)&lt;/code&gt;). The sample is random but the &lt;em&gt;run&lt;/em&gt; is deterministic. A failure names a specific hexagram-and-date I can reproduce forever. Random-but-reproducible is the sweet spot: broad coverage, zero flakiness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolating the layers.&lt;/strong&gt; Notice I feed najia's own computed day-stem into my engine before comparing. That deliberately takes the &lt;em&gt;calendar&lt;/em&gt; out of the equation so this test measures only the &lt;em&gt;charting&lt;/em&gt; rules. The calendar gets its own differential test (below). One oracle per concern; never let two unknowns hide each other.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The payoff: correctness stopped being "I'm pretty sure I read the book right" and became a green check in CI that &lt;strong&gt;anyone&lt;/strong&gt; can re-run. If a contributor changes a lookup table and breaks a rule, 200 charts light up red with the exact hexagram that broke.&lt;/p&gt;
&lt;h2&gt;
  
  
  The foundation had to be diffed too: the calendar
&lt;/h2&gt;

&lt;p&gt;There's a layer &lt;em&gt;below&lt;/em&gt; charting that is even less forgiving. Everything in this system is derived from the &lt;strong&gt;four pillars&lt;/strong&gt; — the stem-branch coordinates of the casting moment (year, month, day, hour). If the &lt;strong&gt;day pillar is off by one&lt;/strong&gt;, every downstream fact — the line strengths, the void days, the month authority — is silently wrong. The whole chart is garbage and nothing throws an error.&lt;/p&gt;

&lt;p&gt;And the bugs all live at the boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Months are divided by solar terms, not the lunar 1st.&lt;/strong&gt; Whether a moment belongs to "this month" or "next" depends on the &lt;em&gt;exact instant&lt;/em&gt; the sun crosses a solar-term boundary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The day changes at 23:00, not midnight.&lt;/strong&gt; In this tradition the next day's pillar begins at the start of the 子 (zǐ) hour — 11 PM — not at 00:00.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-timezone casting.&lt;/strong&gt; Someone casting from California has a wall-clock time that must be reconciled with the absolute instant of a solar term.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Zero-tolerance foundations get the same treatment: I compute with &lt;a href="https://github.com/yuangu/sxtwl_cpp" rel="noopener noreferrer"&gt;&lt;code&gt;sxtwl&lt;/code&gt;&lt;/a&gt; (a high-precision astronomical ephemeris) as the primary, and cross-validate against &lt;a href="https://github.com/6tail/lunar-python" rel="noopener noreferrer"&gt;&lt;code&gt;lunar-python&lt;/code&gt;&lt;/a&gt; — a totally separate implementation. Solar-term crossings, the 23:00 day flip, and timezone edge cases each have unit tests that diff the two libraries. Disagreement surfaces immediately.&lt;/p&gt;
&lt;h2&gt;
  
  
  The principle underneath: only cross-validatable things get into the core
&lt;/h2&gt;

&lt;p&gt;Once you build around differential testing, it starts making &lt;em&gt;design&lt;/em&gt; decisions for you. It became the single rule the whole engine is organized around:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Anything that enters the deterministic core must be independently cross-validatable. Anything unfalsifiable stays out.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I Ching practice has a category called 神煞 (shén shà) — auspicious/inauspicious "stars." Different schools define them differently and the rules openly contradict. There is &lt;strong&gt;no single truth&lt;/strong&gt; and &lt;strong&gt;no independent implementation&lt;/strong&gt; to diff against. So they don't go in the core. Not because they're "wrong" — but because I have no way to prove the code computes them &lt;em&gt;correctly&lt;/em&gt;, and unfalsifiable output has no business sitting next to facts I can guarantee.&lt;/p&gt;

&lt;p&gt;Same reasoning killed "true solar time" correction in the core (which longitude? which equation-of-time formula? no single answer → can't diff), and it's why the engine commits to &lt;strong&gt;one&lt;/strong&gt; school's rules instead of mixing several (mixing = contradictory rules in one engine = unfalsifiable by construction).&lt;/p&gt;

&lt;p&gt;The stuff I deliberately leave out is a feature, not a gap. It's the same discipline that keeps a type system sound: if you can't prove it, don't let it in — layer it out, mark it optional, or hand it to a different layer.&lt;/p&gt;
&lt;h2&gt;
  
  
  The takeaway (works far outside divination)
&lt;/h2&gt;

&lt;p&gt;Strip away the I Ching and this is a pattern for &lt;strong&gt;any&lt;/strong&gt; domain where you can't fully trust your own answer key — spec-compliant parsers, tax and payroll engines, protocol implementations, financial calculators, anything porting a fuzzy standard into code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Name the self-validation paradox.&lt;/strong&gt; Tests you wrote encode the bugs you have. Own it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find an independent oracle.&lt;/strong&gt; A second implementation by someone who never talked to you. Diff every field, not just the final answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two oracles beat one.&lt;/strong&gt; Three-way agreement crushes shared-bug risk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Random but seeded.&lt;/strong&gt; Broad coverage, reproducible failures, zero flake.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolate layers.&lt;/strong&gt; One oracle per concern so two unknowns can't hide each other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let it gate your design.&lt;/strong&gt; If a feature can't be cross-validated, it doesn't belong in the part of the system you call "correct."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The engine is open source (Apache-2.0), the differential tests run in CI, and you can clone it and re-run every claim in this post:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://github.com/yaomancy/liuyao-engine" rel="noopener noreferrer"&gt;github.com/yaomancy/liuyao-engine&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/yaomancy" rel="noopener noreferrer"&gt;
        yaomancy
      &lt;/a&gt; / &lt;a href="https://github.com/yaomancy/liuyao-engine" rel="noopener noreferrer"&gt;
        liuyao-engine
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Deterministic I Ching (Liu Yao / 六爻) hexagram engine — cross-validated, Apache-2.0
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;liuyao-engine&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;确定性&lt;strong&gt;六爻&lt;/strong&gt;装卦/断法引擎 · &lt;em&gt;Deterministic I Ching (Liù Yáo) hexagram engine&lt;/em&gt;
&lt;strong&gt;by &lt;a href="https://yaomancy.com" rel="nofollow noopener noreferrer"&gt;Yaomancy&lt;/a&gt;&lt;/strong&gt; · Apache-2.0&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/yaomancy/liuyao-engine/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/yaomancy/liuyao-engine/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;
&lt;a href="https://www.python.org/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/36cf3d0f7992a33a063d3833577d62204f8934d82b69874c086390608db4947c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f707974686f6e2d332e31312b2d626c75652e737667" alt="Python 3.11+"&gt;&lt;/a&gt;
&lt;a href="https://github.com/yaomancy/liuyao-engine/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a8cb6955fb6e825727774da32760c729c43dcde6b3f19232539089af14348deb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4170616368652d2d322e302d677265656e2e737667" alt="License: Apache-2.0"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;把"起卦时刻"和"六爻"换算成一份&lt;strong&gt;结构化卦盘事实&lt;/strong&gt;：干支四柱 → 装卦（纳甲 / 六亲 / 六神 / 世应 / 伏神）→ 旺衰骨架 → 用神。&lt;strong&gt;纯规则、零网络、可逐项回归验证。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;法度：装卦遵《卜筮正宗》、断法遵《增删卜易》；神煞不进核心逻辑；不混派。引擎只负责一切"硬事实"，不含任何 AI 解读。&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;English quick start&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;liuyao-engine&lt;/code&gt; is a &lt;strong&gt;deterministic engine for Liù Yáo (六爻) / coin-oracle I Ching divination&lt;/strong&gt;. Given a cast and a timestamp it returns a fully assembled, machine-readable hexagram chart — Heavenly-Stem/Earthly-Branch four pillars, najia stem-branch attribution, six-relations, six-spirits, self/other lines, hidden lines, and wàng-shuāi (strength) — as plain Python data. &lt;strong&gt;No AI, no network, no I/O.&lt;/strong&gt; Its lookup tables are cross-validated against &lt;a href="https://github.com/bopo/najia" rel="noopener noreferrer"&gt;&lt;code&gt;najia&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/AdrienSterling/yigram-najia-rules" rel="noopener noreferrer"&gt;&lt;code&gt;yigram-najia-rules&lt;/code&gt;&lt;/a&gt;; the calendar is double-checked against &lt;code&gt;sxtwl&lt;/code&gt; + &lt;code&gt;lunar-python&lt;/code&gt;. Classical terms stay in Chinese (with pinyin) — see the glossary; interpretive meanings are intentionally not translated.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;安装 / Install&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;pip install git+https://github.com/yaomancy/liuyao-engine&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;依赖：仅 &lt;code&gt;sxtwl&lt;/code&gt;（历法）。Python ≥ 3.11。&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;30 秒上手 / Killer example&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-python notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;liuyao&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/yaomancy/liuyao-engine" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;It's the deterministic core behind &lt;a href="https://yaomancy.com" rel="noopener noreferrer"&gt;yaomancy.com&lt;/a&gt;. I pulled it out and open-sourced it precisely because the interesting part isn't the mysticism — it's the engineering discipline of proving a computation correct when nobody can hand you the answer key.&lt;/p&gt;

&lt;p&gt;If you've solved the "no ground truth" problem in your own domain, I'd genuinely like to hear how — the comments are open.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>python</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
