// PROCITY core PRNG — the only randomness source in generation code (CITY_SPEC law). // Same citySeed ⇒ byte-identical city. Math.random() is banned outside cosmetic-only FX. export function xmur3(str) { let h = 1779033703 ^ str.length; for (let i = 0; i < str.length; i++) { h = Math.imul(h ^ str.charCodeAt(i), 3432918353); h = (h << 13) | (h >>> 19); } return function () { h = Math.imul(h ^ (h >>> 16), 2246822507); h = Math.imul(h ^ (h >>> 13), 3266489909); return (h ^= h >>> 16) >>> 0; }; } export function mulberry32(a) { return function () { 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; }; } // Stable uint32 for an entity: seedFor(citySeed, 'shop', 42) export function seedFor(citySeed, kind, id) { return xmur3(`${citySeed}:${kind}:${id}`)(); } // Seeded stream for an entity: const r = rng(citySeed,'shop',42); r() → [0,1) export function rng(citySeed, kind, id) { return mulberry32(seedFor(citySeed, kind, id)); } // Helpers over a stream r export const pick = (r, arr) => arr[(r() * arr.length) | 0]; export const irange = (r, lo, hi) => lo + ((r() * (hi - lo + 1)) | 0); // inclusive ints export const frange = (r, lo, hi) => lo + r() * (hi - lo); export function shuffle(r, arr) { // in-place Fisher-Yates for (let i = arr.length - 1; i > 0; i--) { const j = (r() * (i + 1)) | 0; [arr[i], arr[j]] = [arr[j], arr[i]]; } return arr; }