PROCITY/web/js/core/prng.js
2026-07-14 10:46:40 +10:00

47 lines
1.5 KiB
JavaScript

// 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;
}