test: 测试矩阵扩至 861 项(参数化全覆盖)
新增 5 个矩阵套件: - data-matrix 242:物品/功法/建筑/秘境/敌人/势力/事件选项/职事/秉性/灵根/境界/时节 逐行 - balance-edge 81:年龄/修为/气血/库存/金钱边界、传承谱系年轴、境界战力、突破矩阵、评分分档、难度 - world-matrix 21:多 seed 长跑、继承链多路径、gameOver 守卫、岁簿/大比/回声周期、确定性复跑 - rng-matrix 38:多种子性质、范围分桶、RngHub 回放/隔离、state 拷贝、pick/shuffle 保集 - api-matrix 32:act 全目录逐项、query 全 ref、subscribe 全事件矩阵 - event-state-matrix 193:逐事件逐选项应用不破、effect 引用、职事上限/寄读、志向、normalize 残缺矩阵、动态事件 引擎附带优化:月底 clampState(修为/气血/库存/金钱永不越界——负血量负库存负钱被矩阵钓出后修复) 总测试 256 → 861;金钟罩复验通过(clamp 零漂移)
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Rng, seedToRng, RngHub } from '../src/renderer/game/core/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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user