import { describe, expect, it } from 'vitest' import { Rng, seedToRng } from '../src/renderer/game/engine/kernel/rng' describe('Rng.sfc32 特性', () => { it('同一种子产生的序列完全一致', () => { const a = new Rng(seedToRng('ser1')) const b = new Rng(seedToRng('ser1')) for (let i = 0; i < 5000; i++) { expect(a.next()).toBe(b.next()) } }) it('不同种子产生不同序列', () => { const a = new Rng(seedToRng('seed-a')) const b = new Rng(seedToRng('seed-b')) let diff = 0 for (let i = 0; i < 100; i++) { if (a.next() !== b.next()) diff++ } expect(diff).toBeGreaterThan(90) }) it('输出始终落在 [0,1)', () => { const rng = new Rng(seedToRng('domain')) for (let i = 0; i < 20000; i++) { const v = rng.next() expect(v).toBeGreaterThanOrEqual(0) expect(v).toBeLessThan(1) } }) it('分布均值接近 0.5(20000 样本)', () => { const rng = new Rng(seedToRng('mean')) let sum = 0 const n = 20000 for (let i = 0; i < n; i++) sum += rng.next() const mean = sum / n expect(Math.abs(mean - 0.5)).toBeLessThan(0.02) }) it('int 端点全部可取(长抽样覆盖 min 与 max)', () => { const rng = new Rng(seedToRng('ends')) let minSeen: number | null = null let maxSeen: number | null = null for (let i = 0; i < 5000; i++) { const v = rng.int(1, 6) expect(v).toBeGreaterThanOrEqual(1) expect(v).toBeLessThanOrEqual(6) if (v === 1) minSeen = v if (v === 6) maxSeen = v } expect(minSeen).toBe(1) expect(maxSeen).toBe(6) }) it('pick 返回数组内元素', () => { const rng = new Rng(seedToRng('pick')) const pool = ['a', 'b', 'c'] for (let i = 0; i < 200; i++) { expect(pool).toContain(rng.pick(pool)) } }) it('pick 空数组抛错(进行防御性约束)', () => { const rng = new Rng(seedToRng('empty')) expect(() => rng.pick([])).toThrow() }) it('chance 边界行为:p=0 恒否、p=1 恒是', () => { const rng = new Rng(seedToRng('chance')) for (let i = 0; i < 50; i++) { expect(rng.chance(0)).toBe(false) expect(rng.chance(1)).toBe(true) } }) it('shuffle 保持乱序且不丢元素', () => { const rng = new Rng(seedToRng('shuffle')) const src = Array.from({ length: 30 }, (_, i) => i) const shuffled = rng.shuffle(src) expect(shuffled.length).toBe(30) expect([...shuffled].sort((a, b) => a - b)).toEqual(src) // 30 个元素单次打乱完全与原序相同概率几乎为 0 expect(shuffled.some((v, i) => v !== src[i])).toBe(true) }) it('between 落在区间内', () => { const rng = new Rng(seedToRng('between')) for (let i = 0; i < 500; i++) { const v = rng.between(2.5, 7.5) expect(v).toBeGreaterThanOrEqual(2.5) expect(v).toBeLessThanOrEqual(7.5) } }) it('getState 返回副本:修改不影响原', () => { const rng = new Rng(seedToRng('copy')) rng.next() const snap = rng.getState() snap.a = 999999 const c = new Rng(rng.getState()) expect(c.next()).toBe(rng.next()) }) it('全零种子被安全修复不会退化为常数', () => { const state = { a: 0, b: 0, c: 0, d: 0 } const rng = new Rng(state) const v1 = rng.next() const v2 = rng.next() expect(v1).not.toBe(v2) }) it('空字符串种子可稳定初始化', () => { const a = new Rng(seedToRng('')) const b = new Rng(seedToRng('')) expect(a.next()).toBe(b.next()) }) })