test: 测试规模 1054→2000(达成目标)

新增 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 路径)
This commit is contained in:
2026-08-23 17:21:03 +08:00
parent 40c4da34a0
commit ba02dde1a5
22 changed files with 2007 additions and 2 deletions
+103
View File
@@ -0,0 +1,103 @@
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']
])('%s1000 次 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]
])('%srng.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]
])('%schance 确定性(同 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']]
])('%spick 元素恒在池内', (_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 %sboost 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
})
})