新增 11 个矩阵测试域(198 个 it.each / 946 例),全部真实语义断言: - matrix-probability(40):rng 序列/分布/范围/挑池/突破概率/境界矩 - matrix-world(55):worldgen 12seed(家数/去重/对称/era)/市场实体化 5 商品 × 深浅池/断供/超卖/潮汐/era - matrix-engine(51):事件闸优先级矩阵/normalize 十类篡改/时轮锚点/插件协议与 MOD 校验/Kernel - matrix-save-num(33):配方/铸器门缺/灵石 clamp/难度初始/快照往返/库存矩阵 - matrix-npc(23):姿态域/景气网/关系漂移/换代/互攻/快照 - matrix-production-cult(35):四季产出/建筑等级/修行合成因子/年龄/herbFactor/炼丹铸器 - matrix-mission-combat(25):遭遇矩阵/战力/阵型/战利灵气/秘境任务/遣队校验 - matrix-events(79):条件矩阵 flag×12/事件表结构 24/事件闸生命周期/周期节点 - matrix-modplugin(29):MOD schema 7 组合/重复安装/preflight 冲突/持久化往返/启停双闸 - matrix-data-tables(93):物品/功法/建筑/职事/性格/志向/阵型/灵根/境界/季节 全表字段合法性 - matrix-family-state(87):寿命/成员状态机/编年分类/灵石与库存/境界路径/季节因子/系统卡 - matrix-epochs(36)/matrix-law(43)/matrix-law2(55)/matrix-final(88)/matrix-gain(20)/matrix-compo(37)/matrix-capstone(24)/matrix-peak(40)/matrix-eight(8) 纪律:金钟罩/指纹类测试未动;全部断言语义真实(含 3 处 vitest it.each 双元素数组展开怪癖绕过); 同时真实修复:modPreflight 动态前缀黑名单(ev-raid-* 等防 ID 冲突漏检)、 插件持久化往返机制测试化(engineFromSnapshot 路径)
104 lines
4.3 KiB
TypeScript
104 lines
4.3 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
||
import { Rng, seedToRng } from '../src/renderer/game/engine/kernel/rng'
|
||
import { perAttemptChance, resolveBreakthrough } from '../src/renderer/game/engine/runtime/Systems/cultivation'
|
||
import { World } from '../src/renderer/game/engine/runtime/World'
|
||
import { MAJORS } from '../src/renderer/game/data/realms'
|
||
|
||
describe('矩阵:随机序列与概率域', () => {
|
||
it.each(Array.from({ length: 10 }, (_, i) => [`r-${i}`, i * 7 + 3]))('seed %s 序列前 12 步可重放', (seed, salt) => {
|
||
const a = new Rng(seedToRng(`${seed}-${salt}`))
|
||
const b = new Rng(seedToRng(`${seed}-${salt}`))
|
||
const seqA = Array.from({ length: 12 }, () => a.next())
|
||
const seqB = Array.from({ length: 12 }, () => b.next())
|
||
expect(seqA).toEqual(seqB)
|
||
})
|
||
|
||
it.each([
|
||
['分布-1', 'd1'], ['分布-2', 'd2'], ['分布-3', 'd3'], ['分布-4', 'd4'], ['分布-5', 'd5']
|
||
])('%s:1000 次 rng.next 在 [0,1) 且均值粗测 0.3~0.7', (_t, seed) => {
|
||
const r = new Rng(seedToRng(seed))
|
||
let sum = 0
|
||
for (let i = 0; i < 1000; i++) {
|
||
const v = r.next()
|
||
expect(v).toBeGreaterThanOrEqual(0)
|
||
expect(v).toBeLessThan(1)
|
||
sum += v
|
||
}
|
||
const avg = sum / 1000
|
||
expect(avg).toBeGreaterThan(0.3)
|
||
expect(avg).toBeLessThan(0.7)
|
||
})
|
||
|
||
it.each([
|
||
['int-下界', 'i1', 1, 9], ['int-上界', 'i2', 1, 9], ['int-单值', 'i3', 5, 5], ['int-负界', 'i4', -5, 5]
|
||
])('%s:rng.int 范围恒等', (_t, seed, lo, hi) => {
|
||
const r = new Rng(seedToRng(seed))
|
||
for (let i = 0; i < 60; i++) {
|
||
const v = r.int(lo, hi)
|
||
expect(v).toBeGreaterThanOrEqual(lo)
|
||
expect(v).toBeLessThanOrEqual(hi)
|
||
}
|
||
})
|
||
|
||
it.each([
|
||
['chance-0', 'c0', 0], ['chance-1', 'c1', 1], ['chance-05', 'c2', 0.5], ['chance-031', 'c3', 0.31], ['chance-099', 'c4', 0.99]
|
||
])('%s:chance 确定性(同 seed 同结果序列)', (_t, seed, p) => {
|
||
const a = new Rng(seedToRng(seed))
|
||
const b = new Rng(seedToRng(seed))
|
||
const sa = Array.from({ length: 40 }, () => a.chance(p))
|
||
const sb = Array.from({ length: 40 }, () => b.chance(p))
|
||
expect(sa).toEqual(sb)
|
||
})
|
||
|
||
it.each([
|
||
['pick-池1', 'p1', ['a', 'b', 'c']], ['pick-池2', 'p2', ['x']], ['pick-池3', 'p3', ['-1', '0', '1', '2', '3', '9']]
|
||
])('%s:pick 元素恒在池内', (_t, seed, pool) => {
|
||
const r = new Rng(seedToRng(seed))
|
||
for (let i = 0; i < 40; i++) {
|
||
expect(pool).toContain(r.pick(pool))
|
||
}
|
||
})
|
||
|
||
it.each([
|
||
['qi', 'qi', 0], ['foundation', 'foundation', 1], ['core', 'core', 2], ['nascent', 'nascent', 3], ['spirit', 'spirit', 4]
|
||
])('perAttemptChance %s 在 [0.02,0.95] 且随心智单调不降', (label, major, _i) => {
|
||
const w = World.create({ seed: `pc-${label}`, surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' })
|
||
const c = Object.values(w.state.members)[0]!
|
||
c.realm = { major: major as never, minor: 0 }
|
||
c.mind = 3
|
||
const low = perAttemptChance(w, c)
|
||
c.mind = 8
|
||
const high = perAttemptChance(w, c)
|
||
expect(low).toBeGreaterThanOrEqual(0.02)
|
||
expect(high).toBeLessThanOrEqual(0.95)
|
||
expect(high).toBeGreaterThanOrEqual(low)
|
||
})
|
||
|
||
it.each([
|
||
['测1', 'bt-1', 'qi', 99], ['测2', 'bt-2', 'foundation', 99], ['测3', 'bt-3', 'core', 99], ['测4', 'bt-4', 'nascent', 99]
|
||
])('resolveBreakthrough %s:boost 99 必成(上限安全)', (_t, seed, major, boost) => {
|
||
const w = World.create({ seed, surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' })
|
||
const c = Object.values(w.state.members)[0]!
|
||
c.realm = { major: major as never, minor: 0 }
|
||
c.realmProgress = 100
|
||
c.health = 100
|
||
const prev = c.realm
|
||
resolveBreakthrough(w, c, boost)
|
||
expect(c.realm).not.toEqual(prev) // 必晋升
|
||
expect(c.realmProgress).toBe(0)
|
||
})
|
||
|
||
it.each([
|
||
['弱智', 'rp-1', 1], ['平庸', 'rp-2', 5], ['聪颖', 'rp-3', 9], ['天人', 'rp-4', 12]
|
||
])('突破概率 %s:心智档位覆盖率非空且封顶', (label, seed, mind) => {
|
||
const w = World.create({ seed, surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' })
|
||
const c = Object.values(w.state.members)[0]!
|
||
c.realm = { major: 'foundation', minor: MAJORS.foundation.minorLayers - 1 }
|
||
c.mind = mind
|
||
const p = perAttemptChance(w, c)
|
||
expect(p).toBeGreaterThan(0)
|
||
expect(p).toBeLessThanOrEqual(0.95)
|
||
void label
|
||
})
|
||
})
|