Files
bubblechambersimart/src/rng.js

71 lines
2.2 KiB
JavaScript
Raw Normal View History

2026-05-20 16:53:23 -04:00
/* ============================================================
rng.js deterministic seeding & sampling
Every stochastic decision in the generator draws from a
salted sub-stream so that changing one parameter does not
reshuffle unrelated parts of the scene.
============================================================ */
/* cyrb53 — fast 53-bit string hash. Stable across runs/machines. */
export function cyrb53(str, seed = 0) {
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
for (let i = 0; i < str.length; i++) {
const ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16).padStart(13, '0');
}
/* mulberry32 — tiny fast PRNG, returns [0,1). */
export function mulberry32(seed) {
let a = seed >>> 0;
return function () {
a = (a + 0x6D2B79F5) >>> 0;
let t = a;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
/* makeRng — seed string + salt → independent PRNG stream. */
export function makeRng(seedStr, salt = '') {
const h = cyrb53(seedStr + '::' + salt);
return mulberry32(parseInt(h.slice(0, 8), 16));
}
/* ---------- Distributions ---------- */
/* Standard normal via Box-Muller. */
export function gauss(rng) {
const u = 1 - rng(), v = rng();
return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
}
/* Log-normal — heavy-tailed positive sample. */
export function logNormal(rng, mu, sigma) {
return Math.exp(mu + sigma * gauss(rng));
}
/* Uniform in [lo, hi). */
export function range(rng, lo, hi) {
return lo + (hi - lo) * rng();
}
/* Random int in [lo, hi]. */
export function randInt(rng, lo, hi) {
return lo + Math.floor(rng() * (hi - lo + 1));
}
/* Pick one element. */
export function pick(rng, arr) {
return arr[Math.floor(rng() * arr.length)];
}
/* Bernoulli. */
export function chance(rng, p) {
return rng() < p;
}