// ========================================== // SEEDED RANDOMNESS (LCG) // ========================================== const LCG_MULTIPLIER = 16807; const LCG_MODULUS = 2147483647; // 2^31 - 1 let _seed = 123456789; export const normalizeSeed = (newSeed) => { const numeric = Math.floor(Number(newSeed) || 0); const normalized = Math.abs(numeric) % (LCG_MODULUS - 1); return normalized + 1; }; export const setSeed = (newSeed) => { _seed = normalizeSeed(newSeed); }; export const createSeededRandom = (seed) => { let localSeed = normalizeSeed(seed); return () => { localSeed = (localSeed * LCG_MULTIPLIER) % LCG_MODULUS; return (localSeed - 1) / (LCG_MODULUS - 1); }; }; export const createRandomRange = (rng) => (min, max) => min + rng() * (max - min); export const createRandomStream = (seed) => { const next = createSeededRandom(seed); return { next, range: createRandomRange(next), }; }; export const mixSeed = (...parts) => { let hash = 2166136261; for (const part of parts) { const chunk = String(part ?? ''); for (let i = 0; i < chunk.length; i += 1) { hash ^= chunk.charCodeAt(i); hash = Math.imul(hash, 16777619); } hash ^= 124; hash = Math.imul(hash, 16777619); } return normalizeSeed(hash >>> 0); }; export const createRandomSeed = () => { const cryptoObj = globalThis.crypto; if (cryptoObj?.getRandomValues) { const buffer = new Uint32Array(1); cryptoObj.getRandomValues(buffer); return normalizeSeed(buffer[0]); } return normalizeSeed(Math.floor(Math.random() * (LCG_MODULUS - 1))); }; export const seededRandom = () => { _seed = (_seed * LCG_MULTIPLIER) % LCG_MODULUS; return (_seed - 1) / (LCG_MODULUS - 1); }; export const randomRange = (min, max) => min + seededRandom() * (max - min); // ========================================== // MATH UTILITIES // ========================================== export const clamp = (value, min, max) => Math.min(max, Math.max(min, value)); export const lerp = (a, b, t) => a + (b - a) * t; export const damp = (current, target, smoothing, dt) => lerp(current, target, 1 - Math.exp(-smoothing * dt)); // Avoids branching (if statements) using continuous modulo logic export const wrap01 = (value) => ((value % 1) + 1) % 1; // ========================================== // ANGLE UTILITIES // ========================================== const PI2 = Math.PI * 2; export const angleDifference = (a, b) => { let diff = (b - a) % PI2; diff = (diff + PI2) % PI2; return diff > Math.PI ? diff - PI2 : diff; }; export const signedAngleXZ = (from, to) => { const cross = from.x * to.z - from.z * to.x; const dot = from.x * to.x + from.z * to.z; return Math.atan2(cross, dot); }; // ========================================== // FORMATTING UTILITIES // ========================================== export const formatOrdinal = (index) => { const n = Math.max(1, index); const mod100 = n % 100; if (mod100 >= 11 && mod100 <= 13) return `${n}th`; const suffixes = ['th', 'st', 'nd', 'rd']; const mod10 = n % 10; return `${n}${suffixes[mod10 < 4 ? mod10 : 0]}`; }; export const formatRaceTime = (seconds) => { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60).toString().padStart(2, '0'); const millis = Math.floor((seconds % 1) * 1000).toString().padStart(3, '0'); return `${mins}:${secs}.${millis}`; };