72 lines
2.5 KiB
TypeScript
72 lines
2.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { SeededRNG } from '../src/core/SeededRNG';
|
|
|
|
describe('SeededRNG', () => {
|
|
it('same seed + stream name → identical sequence', () => {
|
|
const a = new SeededRNG(42).stream('patrons');
|
|
const b = new SeededRNG(42).stream('patrons');
|
|
for (let i = 0; i < 100; i++) expect(a.next()).toBe(b.next());
|
|
});
|
|
|
|
it('different seeds diverge', () => {
|
|
const a = new SeededRNG(1).stream('x');
|
|
const b = new SeededRNG(2).stream('x');
|
|
const seqA = Array.from({ length: 10 }, () => a.next());
|
|
const seqB = Array.from({ length: 10 }, () => b.next());
|
|
expect(seqA).not.toEqual(seqB);
|
|
});
|
|
|
|
it('streams are independent — draining one does not shift another', () => {
|
|
const rng1 = new SeededRNG(7);
|
|
const rng2 = new SeededRNG(7);
|
|
rng1.stream('a');
|
|
for (let i = 0; i < 500; i++) rng1.stream('a').next(); // drain a
|
|
const fromDrained = rng1.stream('b').next();
|
|
const fromFresh = rng2.stream('b').next();
|
|
expect(fromDrained).toBe(fromFresh);
|
|
});
|
|
|
|
it('stream() returns the same stream instance per name (stateful)', () => {
|
|
const rng = new SeededRNG(9);
|
|
const first = rng.stream('q').next();
|
|
const second = rng.stream('q').next();
|
|
expect(first).not.toBe(second); // continued, not restarted
|
|
});
|
|
|
|
it('int() covers bounds inclusively and stays in range', () => {
|
|
const s = new SeededRNG(3).stream('ints');
|
|
const seen = new Set<number>();
|
|
for (let i = 0; i < 2000; i++) {
|
|
const v = s.int(1, 6);
|
|
expect(v).toBeGreaterThanOrEqual(1);
|
|
expect(v).toBeLessThanOrEqual(6);
|
|
seen.add(v);
|
|
}
|
|
expect(seen.size).toBe(6);
|
|
});
|
|
|
|
it('chance() distribution sanity', () => {
|
|
const s = new SeededRNG(11).stream('coin');
|
|
let hits = 0;
|
|
for (let i = 0; i < 10_000; i++) if (s.chance(0.3)) hits++;
|
|
expect(hits / 10_000).toBeGreaterThan(0.27);
|
|
expect(hits / 10_000).toBeLessThan(0.33);
|
|
});
|
|
|
|
it('weighted() respects weights and never returns zero-weight entries', () => {
|
|
const s = new SeededRNG(13).stream('w');
|
|
const counts = { a: 0, b: 0, c: 0 };
|
|
for (let i = 0; i < 10_000; i++) {
|
|
counts[s.weighted([['a', 9], ['b', 1], ['c', 0]] as const)]++;
|
|
}
|
|
expect(counts.c).toBe(0);
|
|
expect(counts.a).toBeGreaterThan(counts.b * 5);
|
|
});
|
|
|
|
it('pick/weighted throw on empty/invalid input', () => {
|
|
const s = new SeededRNG(1).stream('e');
|
|
expect(() => s.pick([])).toThrow();
|
|
expect(() => s.weighted([])).toThrow();
|
|
});
|
|
});
|