Files
thzxx ff6df8054c refactor(0.1.14-P1): 内核归位——game/ 按引擎架构重排(零行为漂移)
结构(旧 game/core+engine → 新 engine/ 域):
- engine/kernel/  时钟/随机/插件协议/fxqueue/timesense/format/urgency/guide/names(原 core)
- engine/narrative/  legacy/报告/列传/谱系/年轴(原 core 叙事族)
- engine/runtime/   World/creation/pcgen/ApiFacade/capabilities/pluginManager/boot/clocks + Systems/*(12 系统)
- engine/sim/     Market(未来 WorldSim 同行)
- 旧 game/core、engine/systems、engine/world.ts 等路径全部废弃(无 re-export 兼容层)

验证:35 套件/967 测试全绿(金钟罩三档零漂移=纯搬迁无行为变化)
typecheck 0 error
2026-08-23 13:23:25 +08:00

87 lines
2.8 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { Rng, seedToRng, RngHub } from '../src/renderer/game/engine/kernel/rng'
describe('RNG 多种子矩阵', () => {
it.each([
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'
].map((s) => [s] as const))('种子 %s 前 5 值互异不重复零帧', (seed) => {
const rng = new Rng(seedToRng(seed))
const vals = [rng.next(), rng.next(), rng.next(), rng.next(), rng.next()]
expect(new Set(vals.map((v) => v.toFixed(6))).size).toBeGreaterThanOrEqual(4)
})
it.each([10, 50, 100, 500, 1000].map((n) => [n] as const))('输出序列 %i 个全在 [0,1)', (n) => {
const rng = new Rng(seedToRng('range-' + n))
for (let i = 0; i < n; i++) {
const v = rng.next()
expect(v).toBeGreaterThanOrEqual(0)
expect(v).toBeLessThan(1)
}
})
it.each([3, 7, 20, 99, 1000].map((n) => [n] as const))('int(0,%i) 端点可达', (n) => {
const rng = new Rng(seedToRng('int-' + n))
let lo = Number.MAX_SAFE_INTEGER
let hi = -1
for (let i = 0; i < n * 40; i++) {
const v = rng.int(0, n - 1)
lo = Math.min(lo, v)
hi = Math.max(hi, v)
}
expect(lo).toBe(0)
expect(hi).toBe(n - 1)
})
})
describe('RngHub 矩阵', () => {
it.each([1, 2, 3].map((n) => [n] as const))('rollSeed 第%i 次格式稳定', () => {
expect(RngHub.rollSeed().startsWith('seed-')).toBe(true)
})
it.each([5, 20, 50].map((n) => [n] as const))('audioNoise01 %i 次均为 [0,1)', (n) => {
for (let i = 0; i < n; i++) {
const v = RngHub.audioNoise01()
expect(v).toBeGreaterThanOrEqual(0)
expect(v).toBeLessThan(1)
}
})
it('音频流可回放(同序)', () => {
const a = RngHub.audioNoise01()
const b = RngHub.audioNoise01()
expect(a).not.toBe(b)
})
})
describe('RigRng state 拷贝矩阵', () => {
it.each(['s1', 's2', 's3'].map((s) => [s] as const))('种子 %s 状态快照后继续一致', (s) => {
const rng = new Rng(seedToRng(s))
for (let i = 0; i < 10; i++) rng.next()
const snap = rng.getState()
const con = new Rng(snap)
const r2 = new Rng(seedToRng(s))
for (let i = 0; i < 10; i++) r2.next()
expect(con.next()).toBe(r2.next())
})
})
describe('pick/shuffle 矩阵', () => {
it.each([['a'], ['a', 'b'], ['a', 'b', 'c'], Array.from({ length: 7 }, (_, i) => 'e' + i)])(
'pick 从 %j 不丢元素',
(arr) => {
const rng = new Rng(seedToRng('pick-' + arr.length))
for (let i = 0; i < 30; i++) {
expect(arr).toContain(rng.pick(arr))
}
}
)
it.each([1, 2, 5, 12].map((n) => [n] as const))('shuffle %i 保集不变', (n) => {
const rng = new Rng(seedToRng('sh-' + n))
const src = Array.from({ length: n }, (_, i) => i)
const out = rng.shuffle(src)
expect(out.length).toBe(n)
expect([...out].sort((a, b) => a - b)).toEqual(src)
})
})