/** * llmindex.io — deterministic seeded RNG for item generation/perturbation * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * License: Proprietary — © Simon-Pierre Boucher, all rights reserved */ /** FNV-1a 32-bit hash of a string seed. */ export function hashSeed(seed: string): number { let h = 0x811c9dc5; for (let i = 0; i < seed.length; i++) { h ^= seed.charCodeAt(i); h = Math.imul(h, 0x01000193); } return h >>> 0; } export interface Rng { /** Uniform float in [0,1). */ next(): number; /** Uniform integer in [min,max] inclusive. */ int(min: number, max: number): number; /** Pick one element. */ pick(arr: readonly T[]): T; /** Fisher-Yates shuffle (copy). */ shuffle(arr: readonly T[]): T[]; } /** mulberry32 PRNG — fast, deterministic, good enough for item perturbation. */ export function createRng(seed: string): Rng { let a = hashSeed(seed); const next = (): number => { a |= 0; a = (a + 0x6d2b79f5) | 0; let t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; return { next, int: (min, max) => min + Math.floor(next() * (max - min + 1)), pick: (arr) => { const el = arr[Math.floor(next() * arr.length)]; if (el === undefined) throw new Error('pick from empty array'); return el; }, shuffle: (arr) => { const out = [...arr]; for (let i = out.length - 1; i > 0; i--) { const j = Math.floor(next() * (i + 1)); [out[i], out[j]] = [out[j]!, out[i]!]; } return out; }, }; }