Tane種

@johnmorrisdotca/tane 1.1.1 · 2 entry points · 67 exports

@johnmorrisdotca/tane

addDays below chance checkBlocks CLI_MAX_COUNT cliLanguage CliResult CliSurroundings counted CountedRandom CSV_COLUMNS dailySeed dayKey DayKey dayOfSeed daysBetween daySeed deriveSeed distinctBelow drawSeed DrawSeedOptions fillIn float fork freshSeed fromJSON fromText hashSeed inBlock int isDayKey isSeed Language languageOf MAX_CSV_ROWS mulberry32 nextDayStart normal pick Random randomAt runCli sample SAVE_FORMAT savedStream SavedStream SEED_MOST SeedBlock seededRandom seedFrom shuffle shuffled stateAt step Step StreamPosition STRINGS TaneStrings toCSV toJSON toText VERSION weightedIndex weightedPick

function addDays

addDays(day: DayKey, by: number): DayKey

The day by days after (or, negative, before) a day.

function below

below(random: Random, n: number): number

An integer in [0, n): one draw.

function chance

chance(random: Random, p: number): boolean

True with probability p, from 0 (never) to 1 (always): one draw.

function checkBlocks

checkBlocks(blocks: readonly SeedBlock[], most?: number): SeedBlock[]

The blocks in ascending order, checked: whole numbers, inside 1 to most, none overlapping another. Throws a RangeError naming the first fault.

const CLI_MAX_COUNT

CLI_MAX_COUNT: 10000

The most numbers one run prints.

function cliLanguage

cliLanguage(flag: string | undefined, env?: Record<string, string | undefined>, locale?: string): Language

The language the command line speaks: --lang, or the environment's, or the system's; Japanese for ja…, English for anything else.

type CliResult

type CliResult = { /** 0 when all went well, 1 when what was asked for could not be done, 2 when the command itself was wrong. */ code: 0 | 1 | 2; /** For standard output. */ out: string; /** For standard error. */ err: string; };

What the command line came to.

type CliSurroundings

type CliSurroundings = { /** The environment, for the language (`LC_ALL`, `LC_MESSAGES`, `LANG`) and `NO_COLOR`. */ env?: Record<string, string | undefined>; /** Standard input, when `--stdin` asks for it: one item to a line. */ stdin?: string; /** Whether the output is a terminal that shows colour. `NO_COLOR` and `--no-color` still turn it off. */ colour?: boolean; /** The system's language where the environment na…

What the command line is run in. All of it is optional.

function counted

counted(seed: number, draws?: number): CountedRandom

A seeded stream that counts its own draws, so that it can be written down at any moment and picked up later: const random = counted(42), draw from it as from any stream, then keep random.position(). The numbers are those of mulberry32(seed), draw for draw.

type CountedRandom

type CountedRandom = Random & { /** The seed it was made from, as an unsigned 32-bit integer. */ readonly seed: number; /** How many numbers have been drawn from the seed so far, counting any it started after. */ readonly draws: number; /** Where it is now, as plain data to keep. */ position(): StreamPosition; };

A stream that knows where it is: call it for the next number, as any Random; read seed and draws, or take its position().

const CSV_COLUMNS

CSV_COLUMNS: readonly ["draw", "value", "state"]

The columns of the CSV, in order.

function dailySeed

dailySeed(at: Date, timeZone?: string): number

Today's seed, the date as a number: 2026-09-30 is 20260930. UTC unless a zone is named.

function dayKey

dayKey(at: Date, timeZone?: string): DayKey

The day a moment falls on, in UTC or in the named IANA time zone.

type DayKey

type DayKey = string;

A calendar date, YYYY-MM-DD.

function dayOfSeed

dayOfSeed(seed: number, block: SeedBlock): DayKey | null

The day a seed names inside a block, or null for a seed that names none. The inverse of daySeed.

function daysBetween

daysBetween(from: DayKey, to: DayKey): number

How many days to is after from: negative when it is before.

function daySeed

daySeed(day: DayKey, block: SeedBlock): number

A day's seed inside a block kept for it: the block's first seed plus the date as a number. With a block from 1,000,000,000, 2026-10-03 is 1,020,261,003: still readable, and never a seed drawn at random if the block is reserved when drawing (drawSeed).

function deriveSeed

deriveSeed(seed: number, ...labels: readonly (string | number)[]): number

A seed of its own for one part of something seeded: deriveSeed(game, "deck") and deriveSeed(game, "dice") are two unrelated streams from one game's seed, and adding a third part later moves neither. Labels may be text or integers and are read in order, so deriveSeed(s, "round", 3) is round 3's. Returns an unsigned 32-bit integer.

function distinctBelow

distinctBelow(random: Random, count: number, limit: number): number[]

count different integers below limit, in the order drawn, by drawing again whenever a number comes up twice. The draw count therefore depends on the luck of the stream; prefer sample for new code, and keep this where a stored seed already depends on it.

function drawSeed

drawSeed(random: Random, options?: DrawSeedOptions): number

A seed from 1 to most, any of them as likely as another, skipping every reserved block: one draw. The draw is spread over the seeds that are left and then stepped over each block below it, so no seed is ever likelier than another and no draw is wasted.

type DrawSeedOptions

type DrawSeedOptions = { /** The largest seed to draw. `SEED_MOST` unless said. */ readonly most?: number; /** Blocks a drawn seed never lands in. They must not overlap. */ readonly reserved?: readonly SeedBlock[]; };

Where a drawn seed may land.

function fillIn

fillIn(template: string, values: Record<string, string | number>): string

Put values into a string's braces: fillIn("Seed {seed}", { seed: 42 }) is "Seed 42". A brace with no value is left as it is.

function float

float(random: Random, min: number, max: number): number

A number in [min, max): one draw.

function fork

fork(random: Random): Random

A new, independent stream split off another: it takes one draw from random and seeds a fresh generator from it. Use it to hand a part of the work its own stream, so drawing more there never shifts what comes after. For a sub-stream named by a label rather than by order, use deriveSeed.

function freshSeed

freshSeed(options?: DrawSeedOptions): number

A new seed for something nobody asked for by number, drawn from Math.random.

function fromJSON

fromJSON(text: string): StreamPosition | null

A position from JSON that toJSON wrote. Nothing in it is trusted: the seed and the draws must be whole numbers in range, the algorithm (when named) must be mulberry32, and the state (when given) must be the one that seed and those draws come to. Null when the text is not JSON, is not a position, does not add up, or is of a later format than this version reads.

function fromText

fromText(text: string): StreamPosition | null

A position from a line toText wrote, with any space around it. Null for anything else, or for a later format.

function hashSeed

hashSeed(text: string): number

A seed from any text: hashSeed("room-7") is the same unsigned 32-bit integer everywhere. FNV-1a over the UTF-16 code units, then MurmurHash3's finaliser, so "a" and "b" land far apart. Use it to seed from a name, a room code or a date string.

function inBlock

inBlock(seed: number, block: SeedBlock): boolean

Whether a seed falls inside a block.

function int

int(random: Random, min: number, max: number): number

An integer from min to max, both included: one draw.

function isDayKey

isDayKey(text: string): boolean

Whether a text is a real date written YYYY-MM-DD: 2026-02-30 is not.

function isSeed

isSeed(value: unknown, most?: number): value is number

Whether a value read from outside (an address, a request body) is a seed: an integer from 1 to most.

type Language

type Language = "en" | "ja";

The languages Tane speaks.

function languageOf

languageOf(tag: string | null | undefined): Language

The language a tag such as ja-JP or en_US.UTF-8 names: Japanese for anything that starts ja, English otherwise.

const MAX_CSV_ROWS

MAX_CSV_ROWS: 100000

The most rows toCSV writes at once.

function mulberry32

mulberry32(seed: number): Random

A seeded stream: mulberry32(42) returns the same numbers every time it is made. The seed is read as an unsigned 32-bit integer, so any integer works and 2^32 + 1 is the same seed as 1.

function nextDayStart

nextDayStart(at: Date, timeZone?: string): Date

The first moment of the day after the one at falls on, in that zone: when a daily seed next changes. Found by searching the next 27 hours for the millisecond the date turns, so daylight saving and half-hour zones need no special case.

function normal

normal(random: Random, mean?: number, spread?: number): number

A number from a normal distribution (Box–Muller): two draws.

function pick

pick<T>(random: Random, items: readonly T[]): T

One item of a list, each as likely as the next: one draw. Throws on an empty list.

type Random

type Random = () => number;

A number in [0, 1), like Math.random, from a stream a seed fixes.

function randomAt

randomAt(seed: number, draws?: number): Random

A stream that starts draws numbers into seed's: randomAt(42, 7)() is the eighth number of mulberry32(42), reached without drawing the first seven. Use it to pick a stream up from a stored position.

function runCli

runCli(args: readonly string[], around?: CliSurroundings): CliResult

Run the command line. See tane --help for what it takes. One seed serves the whole run, so the same command prints the same lines on every machine.

function sample

sample<T>(random: Random, items: readonly T[], count: number): T[]

count items of a list, no item twice, in the order drawn: a partial Fisher–Yates, one draw each. Asking for more than the list holds gives the whole list, shuffled.

const SAVE_FORMAT

SAVE_FORMAT: 1

The shape of the JSON and the text this package writes. It goes up only when a reader of the old shape would be wrong about the new one.

function savedStream

savedStream(position: StreamPosition): SavedStream

A position as the object toJSON writes: the format, the seed, the draws and the state they come to.

type SavedStream

type SavedStream = { /** The shape of this object: `SAVE_FORMAT`. */ format: typeof SAVE_FORMAT; /** What wrote it, such as `"tane 1.1.1"`. For people; nothing reads it back. */ generator: string; /** The algorithm. Always `"mulberry32"`. */ algorithm: "mulberry32"; seed: number; draws: number; /** The generator's state at that position: `stateAt(seed, draws)`. Checked on the way back in. */ state: number; };

A stream's position as toJSON writes it.

const SEED_MOST

SEED_MOST: number

The largest seed: it travels as a plain integer, so it stays a positive 32-bit signed one.

type SeedBlock

type SeedBlock = { readonly from: number; readonly size: number };

A range of seeds kept aside: from up to, not including, from + size.

function seededRandom

seededRandom: (seed: number) => Random

A seeded stream. The name to reach for; mulberry32 is the same function, for anyone who wants to say which.

function seedFrom

seedFrom(input: string | number): number

A seed from whatever a person typed or a link carried. A whole number from 0 to 2^32 − 1, as a number or as its digits, is that seed; any other text is hashed with hashSeed, so "table-7" is a seed as good as 42. Space around text is dropped first. Note that the text "42" and the number 42 are the same seed, and that hashSeed("42") is a different one.

function shuffle

shuffle<T>(random: Random, items: T[]): T[]

The list in place, in a random order (Fisher–Yates, from the end): one draw for each item but the first. Returns the same array.

function shuffled

shuffled<T>(random: Random, items: readonly T[]): T[]

A shuffled copy; the list given is left alone. The same order shuffle would make.

function stateAt

stateAt(seed: number, draws?: number): number

The generator's state after draws numbers have been taken from seed: an unsigned 32-bit integer. It is what step hands back as state after that many steps, worked out in one go. mulberry32(stateAt(seed, n)) carries on from the n-th draw.

function step

step(state: number): Step

One step of mulberry32 as a pure function: the number drawn and the state to keep for the next one. step(seed) gives the same first number as mulberry32(seed)(), so a stream can be stored between requests as one integer and picked up exactly where it stopped.

type Step

type Step = { readonly value: number; readonly state: number };

One draw and the state after it, for code that keeps the state itself (in a database row, say).

type StreamPosition

type StreamPosition = { /** The seed the stream was made from, as an unsigned 32-bit integer. */ readonly seed: number; /** How many numbers have been drawn: 0 for a stream nothing has been taken from. */ readonly draws: number; };

Where a stream is: the seed it started from, and how many numbers have been drawn from it.

const STRINGS

STRINGS: Record<Language, TaneStrings>

Every string, in both languages.

type TaneStrings

type TaneStrings = { /** The command line's help, whole. */ cliUsage: string; cliUnknown: string; cliNeeds: string; cliTryHelp: string; cliCountBad: string; cliSkipBad: string; cliIntBad: string; cliLangBad: string; cliZoneBad: string; cliBoth: string; cliOneJob: string; cliNothing: string; cliSampleBad: string; cliFresh: string; cliToday: string; cliNext: string; cliPositionBad: string; pagePitch: string; pageName:…

The names of the strings. Each is one line or one block of text.

function toCSV

toCSV(position: StreamPosition, count: number): string

The next count numbers from a position as CSV for a spreadsheet: a header, then one row a draw, with the draw's number counted from 1, its value, and the generator's state after it. Lines end CRLF, as RFC 4180 has it.

function toJSON

toJSON(position: StreamPosition): string

A position as JSON, two spaces deep, with the format's number first. fromJSON reads it back.

function toText

toText(position: StreamPosition): string

A position as one line to paste into a chat or a bug report: tane:1:42:7 is format 1, seed 42, 7 draws in.

const VERSION

VERSION: "1.1.1"

The version of this package, as package.json has it. A test holds the two together.

function weightedIndex

weightedIndex(random: Random, weights: readonly number[]): number

An index into weights, each chosen in proportion to its weight: one draw. Weights must not be negative; a list whose weights sum to nothing gives 0.

function weightedPick

weightedPick<T>(random: Random, items: readonly T[], weights: readonly number[]): T

One item, chosen by the matching weight: one draw.

@johnmorrisdotca/tane/react

DailySeed useDailySeed useSeeded

type DailySeed

type DailySeed = { readonly day: DayKey; readonly seed: number };

Today, as useDailySeed hands it back: the day written YYYY-MM-DD, and its seed.

function useDailySeed

useDailySeed(timeZone?: string): DailySeed

Today's day and seed, which change on their own at midnight in timeZone (UTC unless named): a page left open overnight moves to the new day's puzzle without a reload.

On the server this reads the server's clock; a page rendered just before midnight and hydrated just after will show the new day once it hydrates.

function useSeeded

useSeeded<T>(seed: number, make: (random: Random): T) => T

Something made once from a seeded stream, and made again only when the seed changes: useSeeded(seed, (random) => shuffled(random, deck)). The stream is fresh each time, so the result depends on the seed alone, never on how many times the component rendered. make is read when the seed changes; for anything else it depends on, give the component a key.