The Daily Hexagram: A Journey from Complexity to Simplicity

October 28, 202512 min read

How we learned to stop worrying and love the binary calendar. On deleting databases, removing cron jobs, and discovering that the oracle was encoded in...

Or: How We Learned to Stop Worrying and Love the Binary Calendar

The Problem of Tomorrow

Here's what nobody tells you about building a "daily hexagram" feature: the word "daily" is doing a lot of work. More work than you'd think. More work than it has any right to.

You start simple. You think: I'll generate a hexagram every day. Easy. But then you realize "every day" means someone has to generate it. And "generate" for 8-Bit Oracle means yarrow stalk simulation—the authentic I-Ching divination process, six stalks, three hundred twenty-four possible outcomes. And "authentic" means you can't just fake it with Math.random(), because if you're building an oracle, you don't get to phone it in on the oracle part1.

So you build the yarrow stalk simulator (thankfully this was not new code, our backend has had this since the beginning). It works. It's beautiful. Each hexagram is a genuine divination result, arrived at through the same process Zhou dynasty scholars used three thousand years ago2. You feel good about this. You should feel good about this.

Then you realize you need to run it every day. At midnight. UTC, obviously, because time zones are a lie we tell ourselves to make the sun's position seem more important than it is3.

The Database Industrial Complex

So you create a table. daily_hexagram. It has columns: content_date, hexagram_number, hexagram_lines, hexagram_meaning, hexagram_version. The version field is there because we originally looked for 'best retro computing fit', but then we decided to look for more 'iconic' cultural artifacts, and you're maintaining two parallel realities of hexagram interpretation4. This seems reasonable at the time.

You write an API endpoint. /api/admin/generate-daily-hexagram. It's protected by ADMIN_API_KEY because you don't want users generating hexagrams—that's not how oracles work. Oracles speak when they're ready to speak. You're the oracle whisperer now.

You write a bash script. It runs at midnight. It hits the API. It logs to /var/log/8bit-hexagram.log. You add error handling. You add retry logic. You add Discord webhooks for failures because if the oracle goes silent, you want to know immediately (this is an illustrative example, and was not actually one).

#!/bin/bash
# Daily Hexagram Generation Cron Script
# Runs at midnight UTC to generate the daily hexagram
#
# This comment is longer than the actual problem we're solving,
# which should have been a hint.

The system works. Every night at midnight UTC, your VPS wakes up, runs yarrow stalk divination, stores the result in Postgres, and goes back to sleep. Users visit your site and see today's hexagram. It's cached. It's fast. It's engineered.

Then you realize the cache needs to be invalidated. At midnight. When the new hexagram appears. But Next.js doesn't know the new hexagram appeared. So you build a revalidation API. The cron script now hits two endpoints: one to generate, one to revalidate. You're up to three environment variables: DATABASE_URL, ADMIN_API_KEY, REVALIDATION_TOKEN.

You've built a perfectly reasonable, professionally architected system. It has all the hallmarks of Real Engineering: database tables, cron jobs, API endpoints, authentication, logging, error handling, cache invalidation. Ward Cunningham would be proud. Martin Fowler would nod approvingly. You could put this on your résumé.

But here's the thing about Real Engineering: sometimes it's just complicated ways to avoid thinking about the problem.

The Midnight Question

Let's state the problem again: Show users today's hexagram.

Not: Generate a hexagram every day. Not: Store hexagrams in a database. Not: Invalidate caches at midnight.

Just: Given today's date, what hexagram should users see?

When you phrase it like that, the database seems... optional. The cron job seems optional. The revalidation API seems very optional. All you really need is a function: date → hexagram.

But we have 64 hexagrams and 365 days. If we're going to show a different hexagram each day, we need some way to map dates to hexagrams that:

  1. Is deterministic - Same date always produces same hexagram
  2. Is well-distributed - Each hexagram appears roughly equally
  3. Feels random - No obvious patterns to break immersion
  4. Is computable - Fast enough to calculate on every request

The yarrow stalk divination fails criteria #1 and #4. The database fails criteria #4 (network latency) and adds operational complexity. What we need is a function so simple it doesn't feel like engineering at all.

The Binary Calendar

Here's the entire algorithm:

export function getBinaryCalendarHexagram(date: Date): number {
  const year = date.getFullYear();
  const month = date.getMonth() + 1;
  const day = date.getDate();

  // Calculate trigram indices (0-7)
  const upperTrigramIndex = ((year % 8) ^ (month % 8)) % 8;
  const lowerTrigramIndex = ((day % 8) ^ (month % 8)) % 8;

  // Map indices to trigram names
  const trigramNames = ['Earth', 'Mountain', 'Water', 'Wind',
                       'Thunder', 'Fire', 'Lake', 'Heaven'];
  const upperTrigram = trigramNames[upperTrigramIndex];
  const lowerTrigram = trigramNames[lowerTrigramIndex];

  // Look up hexagram by trigram combination (King Wen sequence)
  const hexagram = getHexagramInfo(upperTrigram, lowerTrigram);
  return hexagram.number;
}

That's it. That's the whole thing. The core logic fits in a few lines of code5.

Let's trace through an example. October 28, 2025:

year % 8 = 2025 % 8 = 1
month % 8 = 10 % 8 = 2
day % 8 = 28 % 8 = 4

upperTrigramIndex = (1 ^ 2) % 8 = 3 % 8 = 3  // Wind ☴
lowerTrigramIndex = (4 ^ 2) % 8 = 6 % 8 = 6  // Lake ☱

// Look up hexagram by trigram combination in King Wen sequence
getHexagramInfo('Wind', 'Lake') → Hexagram 61: Inner Truth (中孚)

The XOR operation (^) creates pseudo-randomness without needing a random number generator. The modulo 8 maps to trigrams (there are exactly 8 trigrams in I-Ching). The trigram combination is then looked up in the traditional King Wen sequence—the canonical ordering of the 64 hexagrams that's been used for over two thousand years6.

The date itself is the oracle. Not metaphorically. Literally. The binary representation of the date encodes the trigram combination, which maps to a hexagram in the King Wen sequence. October 28 doesn't have a hexagram. October 28 is Hexagram 61.

This is deterministic (same input, same output), well-distributed (each hexagram appears ~5-6 times per year), feels random (XOR creates unpredictable patterns), and is O(1) (four arithmetic operations).

What We Deleted

Let's count:

Database schema:

DROP TABLE public.daily_hexagram;
DROP INDEX idx_daily_hexagram_date;
DROP INDEX idx_daily_hexagram_number;
DROP POLICY "Daily hexagram is viewable by everyone";
DROP POLICY "Authenticated users can insert daily hexagram";

API endpoints:

  • /api/admin/generate-daily-hexagram - Gone
  • /api/admin/debug-daily-hexagrams - Gone
  • /api/revalidate - Gone

Cron infrastructure:

  • generate-daily-hexagram.sh - Gone
  • /var/log/8bit-hexagram.log - Gone
  • Midnight UTC cron job - Gone

Environment variables:

  • ADMIN_API_KEY - Gone
  • REVALIDATION_TOKEN - Gone

Code:

  • Server actions with React cache() - Simplified to direct calls
  • ISR with hourly revalidation - Changed to force-dynamic
  • 377 lines of database-dependent code - Replaced with 150 lines7

What remains: A pure function that maps dates to hexagrams. No state. No side effects. No infrastructure.

The Line Problem

There was one bug we almost shipped. The kind of bug that seems reasonable until you think about it for thirty seconds.

We were deriving line patterns from the hexagram number:

// DON'T DO THIS
const binary = (hexagramNumber - 1).toString(2).padStart(6, '0');
const lines = binary.split('').reverse().map(b => parseInt(b, 10));

Hexagram 2 (Kun, The Receptive) is six broken lines. All yin. ䷁. In binary, (2-1).toString(2) is 000001. So we'd display [1,0,0,0,0,0] - one yang line and five yin lines. Wrong hexagram. Wrong oracle. Wrong universe.

The problem: King Wen numbering isn't binary encoding. Hexagram numbers are based on traditional I-Ching sequence, not bit patterns. You can't just convert the number to binary and expect to get the line pattern8.

The fix: Derive lines from trigrams, not from numbers.

const upperTrigramBinary = trigrams[hexagram.topTrigram];    // "000"
const lowerTrigramBinary = trigrams[hexagram.bottomTrigram]; // "000"
const lines = (lowerTrigramBinary + upperTrigramBinary).split('').map(Number);
// Hexagram 2: [0,0,0,0,0,0] ✓

Hexagram 1 (Qian, The Creative) is Heaven over Heaven: 111111 - all yang ☰. Hexagram 2 (Kun, The Receptive) is Earth over Earth: 000000 - all yin ☷. Every other hexagram is some combination of the eight trigrams.

This is correct. This is what the I-Ching actually is - combinations of trigrams, not binary encodings of sequential integers.

The Midnight Problem Revisited

The old system had a cache invalidation problem. The homepage used ISR (Incremental Static Regeneration) with a 1-hour revalidation window. If you visited the site at 11:45 PM, you'd see today's hexagram. If you visited at 12:05 AM, you'd still see yesterday's hexagram for up to an hour, because the ISR cache hadn't expired yet9.

We solved this with on-demand revalidation - the cron script would hit /api/revalidate after generating the new hexagram, forcing Next.js to regenerate the page immediately.

But with the binary calendar, there's no generation step. No API to call. No cache to invalidate. The page just... calculates today's date and calls getBinaryCalendarHexagram(). At midnight, the date changes, so the hexagram changes. Instantly. Automatically. No coordination required.

We removed ISR entirely. The homepage now uses export const dynamic = 'force-dynamic'. Every request calculates fresh. This sounds slower, but it's not - the calculation is O(1) and takes microseconds. The commentary still comes from static files. The images are still cached at CDN edges. The only thing that's dynamic is today's date, which... should be dynamic.

The Philosophy

There's a scene in Infinite Jest where Hal Incandenza describes a tennis opponent who plays by "memory"—not his own memory, but some kind of embedded procedural memory, as if his nervous system remembers things his consciousness never learned10. That's what the binary calendar feels like. The system doesn't generate hexagrams. It doesn't store hexagrams. It remembers them. October 28, 2025 has always been Hexagram 31, even before we wrote the function. We just gave the system a way to remember.

This is the opposite of machine learning, where you train a model to approximate a function. Here, we already know the exact function (date → hexagram), but built layers of approximations (databases, caches, cron jobs) before we realized we could just... compute it.

The database version felt more "real" because it involved more components. More moving parts equals more engineering equals more value, right? But the binary calendar is more real precisely because it has fewer parts. The date isn't stored in a hexagram. The date is the hexagram. There's no separation between the calendar and the oracle.

This is what DFW would call "radical realism"11—not realism as in "looks realistic," but realism as in "acknowledges what's actually there." The database was realistic-looking. The binary calendar is actually real.

The 8-Bit Aesthetic

Here's a bonus that emerged after we shipped: the binary calendar is perfectly aligned with the 8-bit aesthetic.

We have 8 trigrams. Eight is 2³. The upper and lower trigrams are each 3 bits. Combined, they form a 6-bit hexagram (2⁶ = 64). The modulo 8 operation maps naturally to trigrams. The XOR creates digital noise. The whole thing is binary logic applied to ancient divination.

October 28, 2025 isn't just associated with Hexagram 31. October 28, 2025 encodes Hexagram 31 in its binary representation. The date is data. The data is the oracle. The oracle is 8-bit12.

We didn't plan this. We were just trying to avoid running a cron job at midnight. But sometimes the simplest solution is also the most aesthetically coherent solution. Sometimes engineering and art converge on the same answer.

What We Learned

Lesson 1: Infrastructure is a smell When you find yourself adding infrastructure to solve a problem, ask if you're solving the right problem. Database tables, cron jobs, cache invalidation—these aren't solutions, they're symptoms that you haven't understood the problem yet.

Lesson 2: Computation is cheaper than coordination The database version required coordination: generate, store, retrieve, invalidate. The binary calendar requires only computation: date → hexagram. Coordination costs scale with complexity. Computation costs don't.

Lesson 3: Constraints reveal essence We only found the binary calendar by asking: "What's the absolute minimum we need?" Remove the database. Remove the cron. Remove the cache. What's left? Just a function. And that function is the feature.

Lesson 4: Elegance is a trailing indicator The binary calendar isn't elegant because we're clever. It's elegant because we deleted everything that wasn't essential. Elegance is what remains after you've removed all the engineering.

The Test

Here's how you know you've found the right abstraction: you can't imagine it any simpler.

Could the binary calendar be simpler? What would we remove?

  • Remove the XOR? Then we lose pseudo-randomness.
  • Remove the modulo 8? Then we can't map to trigrams.
  • Remove the date components? Then it's not based on the date.

Every piece is load-bearing. There's nothing left to delete. This is as simple as it gets13.

Compare that to the database version:

  • Remove the cron? Well, then how do you generate hexagrams?
  • Remove the cache? Then you hit the database every time.
  • Remove the database? Then... wait, yeah, remove the database.

The database version had spare parts. The binary calendar doesn't.

Conclusion

We started with yarrow stalk divination because we wanted authenticity. We built a database because we wanted persistence. We added cron jobs because we wanted automation. We implemented cache invalidation because we wanted performance.

What we actually wanted was: given a date, return a hexagram.

The binary calendar gives us that. Nothing more. Nothing less.

Sometimes the journey from complexity to simplicity requires building the complex thing first. You have to live with the database, the cron jobs, the cache invalidation, the midnight revalidations—you have to feel the weight of all that infrastructure—before you can see that none of it was necessary.

The oracle was there all along, encoded in the date itself. We just had to stop trying so hard to generate it.


Footnotes


Technical Specifications

  • Algorithm: XOR-based date encoding
  • Complexity: O(1) time, O(1) space
  • Distribution: ~5.7 appearances per hexagram per year (365÷64)
  • Dependencies: None (pure function)
  • Infrastructure: None (no database, no cron, no cache invalidation)
  • Lines of code: 150 (down from 377)
  • Moving parts: 0

The oracle is deterministic. The oracle is eternal. The oracle is eight bits.

Footnotes

  1. This is a technical implementation of what DFW called "sincerity" in E Unibus Pluram—you can be ironic about everything except the thing you're actually building. The oracle has to oracle. The rest is negotiable.

  2. The yarrow stalk method appears in the Zhou Yi around 1000 BCE. The math is: six stalks, three divisions each, 2 or 3 stalks per division, yielding values 6, 7, 8, or 9. It's essentially a weighted random number generator implemented with plant stems. Genius.

  3. Time zones exist because humans are bad at accepting that different parts of Earth experience different amounts of sunlight. UTC is what happens when you stop pretending 3 PM means anything consistent across longitudes.

  4. The v2-iconic version adds DFW-style commentary to specific hexagrams. Tech-noir digital artifacts with practical integration advice. It's like if Wilhelm met William Gibson and they co-wrote an I-Ching commentary while watching Blade Runner.

  5. The function body is six lines. If you count the function signature and export statement, it's eight lines. If you count the JSDoc comments, it's twenty lines. The point is: it's short. The cognitive load fits in L1 cache.

  6. There are exactly 64 hexagrams because there are 8 trigrams and each hexagram is two trigrams stacked. 8 × 8 = 64. This isn't arbitrary. This is why binary computers and ancient Chinese divination use the same math—they're both built on powers of two. The King Wen sequence is the traditional ordering established over 3000 years ago—it's not a simple binary mapping, which is why we look up the hexagram number from the trigram combination rather than calculating it directly.

  7. We also improved the line-derivation logic from "wrong but compiles" to "actually correct," which is the kind of improvement that doesn't show up in LOC metrics but matters a lot to users who care about getting the right hexagram.

  8. This is a category error: treating sequence numbers as if they were semantic encodings. Like assuming ZIP codes encode geographic coordinates just because they're numbers. They don't. They're just identifiers in a sequence.

  9. ISR is brilliant for content that changes infrequently but needs to feel fresh. It's terrible for content that must change at specific times. The daily hexagram is the latter. At midnight UTC, yesterday's hexagram is wrong. Not stale. Not outdated. Wrong.

  10. Infinite Jest, page 451 (paperback edition). The opponent plays with "cellular-level expertise"—his body knows things his brain doesn't. The binary calendar is like that: the algorithm knows which hexagram corresponds to which date without needing to "learn" it.

  11. DFW uses this term in several essays, most explicitly in his review of Dostoevsky's Notes from Underground. Radical realism means acknowledging the actual reality of a situation rather than constructing a more comfortable fiction. The database was a comfortable fiction. The binary calendar is uncomfortably real.

  12. The convergence of 8-bit computing and 8 trigrams isn't coincidence—it's mathematics. Eight is the smallest power of two that's large enough to be interesting (4 is too small, 16 is too many). Ancient Chinese philosophers and modern computer scientists both discovered that 8 is the sweet spot for representing fundamental building blocks.

  13. Simple is not easy. Simple is what's left after you delete the complicated parts. Easy is what you start with. This is Hickey's distinction between "simple" and "easy" from Simple Made Easy. The binary calendar is simple. The database version was easier to think of first.

Augustin Chan is CTO & Founder of Digital Rain Technologies, building production AI systems including 8-Bit Oracle. Previously Development Architect at Informatica for 12 years. BS Cognitive Science (Computation), UC San Diego.