MAMATHA EPILI / ENGINEERING & AI
Selected work

Open-source SDK2026Published

tarot-spread-sdk

Deterministic draws, and prose without a model

Stack

Frontend
  • TypeScript with complete declarations
  • ESM and CJS bundles, tree-shakeable (`sideEffects: false`)
  • Runs in browsers, workers and React Native as well as Node
  • Angular demo app, published as the live playground
Backend & Data
  • Node 18+, zero runtime and zero peer dependencies
  • FNV-1a hashing into a Mulberry32 PRNG
  • Full 78-card Rider-Waite-Smith dataset embedded, no network calls
  • Offline narrator plus structured prompt contexts for optional LLM synthesis
  • Vitest across eight suites — PRNG, deck, spreads, daily, narrator, synthesizer
  • A measured bundle-size budget in CI rather than an estimated one

Try it here

Live — runs the project’s own code

The published SDK, running in your browser. Draw a card of the day, ask a yes/no question, or pick from a fanned deck of 78 — every reading is composed offline by the narrator, with no key and no backend.

Open the demo in its own tab Served from mamta-epili.github.io, sandboxed.

Problem

A daily-draw feature sounds trivial until you need the same date to return the same card for the same person in every timezone and on every device, and until the prose has to arrive without a model call on the critical path. I wanted the randomness to be reproducible and the interpretation to work offline, with an LLM as an upgrade rather than a dependency.

What I built

  • A deterministic draw: a date and a user id are hashed with FNV-1a into a Mulberry32 generator, so the same tuple yields the same card in any timezone and any runtime.
  • An offline narrator that composes full reader-voice interpretations from the embedded dataset, with no model and no network.
  • Deck and spread engines covering instant auto-draws and interactive picking from a face-down deck of 78.
  • AI as an optional layer: polarity confidence scoring, elemental balance analysis and structured prompt contexts for a model that may or may not be there.
  • The complete Rider-Waite-Smith dataset embedded in the bundle, so a reading never depends on a fetch.
  • An Angular demo published as the live playground, running the SDK with no key and no backend.

Technical decisions & trade-offs

  • Zero dependencies, deliberately. Every transitive package is code nobody reviewed running in your build; a divination SDK has no business widening a supply chain, so the PRNG, shuffle, narrator and dataset are all first-party.
  • Determinism is seeded from the domain, not from a clock: hashing `(date, userId)` means the result is reproducible and testable, where `Math.random()` would be neither.
  • The narrator ships before the LLM. Prose that requires a model call is prose that fails when the key does, so synthesis is the upgrade path rather than the default.
  • The dataset is embedded rather than fetched — 18 KB of the 26 KB gzipped bundle — because a card meaning arriving over the network is a loading state nobody wants mid-reading.
  • The bundle-size budget is measured in CI, not estimated. A number nobody checks is a number that drifts.

Challenges solved

  • Getting the same draw across timezones meant deriving the date key deliberately rather than trusting a local `Date`, which silently differs either side of midnight.
  • Writing a narrator that reads like a person without a model: the phrasing has to vary with the card and position while staying deterministic, so the variation comes from the seed rather than from chance.

Outcome

Published under MIT with zero dependencies and eight test suites, and a live demo where every reading is composed in the browser — no key, no backend, nothing to install.

Permalink: https://www.mamathaepili.com/projects/tarot-spread-sdk