新增 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 路径)
105 lines
5.3 KiB
TypeScript
105 lines
5.3 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
||
import { World } from '../src/renderer/game/engine/runtime/World'
|
||
import { modParse } from '../src/renderer/game/engine/runtime/modSchema'
|
||
import { modToPlugin, modPreflight } from '../src/renderer/game/engine/runtime/modManager'
|
||
import { examplePlugin } from '../src/renderer/game/engine/runtime/demo-plugins'
|
||
import { registerPluginFactory } from '../src/renderer/game/engine/runtime/World'
|
||
import { engineFromSnapshot } from '../src/renderer/game/engine/GameEngine'
|
||
|
||
function mk(seed: string): World {
|
||
return World.create({ seed, surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' })
|
||
}
|
||
|
||
const TEXT = (id: string, extra: Record<string, unknown> = {}) =>
|
||
JSON.stringify({ id, name: id, version: '1.0.0', schema: 1, author: 'tester', data: { ...extra } })
|
||
|
||
describe('矩阵:MOD schema 组合', () => {
|
||
it.each([
|
||
['空数据', {}],
|
||
['纯事件', { events: [{ id: 'ev-a1', name: 'x', category: 'daily', weight: 2, text: 't', options: [{ label: 'a', eff: {} }] }] }],
|
||
['纯模板', { npcs: [{ id: 'n-t22', name: 't22氏', region: 'r', style: 's', desc: 'd', leaderRealm: 'foundation', initialPower: 150, powerGrowth: [3, 8] }] }],
|
||
['纯词库', { worldgen: { fams: ['电'], regions: ['电谷'] } }],
|
||
['纯丹方', { pills: [{ output: 'p-t1', name: 'x', danfangLevel: 1, stones: 10, lingcao: 5, beastcore: 0, desc: '' }] }],
|
||
['纯灾因', { calamities: [{ name: '试灾', family: { lingcao: 0.9 }, effect: { beastcore: -0.1 } }] }],
|
||
['全混合', {
|
||
events: [{ id: 'ev-mix1', name: 'x', category: 'daily', weight: 1, text: 't', options: [{ label: 'a', eff: {} }] }],
|
||
npcs: [{ id: 'n-mix1', name: '混氏', region: 'r', style: 's', desc: 'd', leaderRealm: 'qi', initialPower: 100, powerGrowth: [2, 6] }],
|
||
worldgen: { fams: ['混'] },
|
||
pills: [{ output: 'p-mix1', name: 'x', danfangLevel: 1, stones: 5, lingcao: 3, beastcore: 0, desc: '' }],
|
||
calamities: [{ name: '混灾', family: {}, effect: {} }]
|
||
}]
|
||
])('MOD %s:安装→清单/导出/回滚闭环', (label, data) => {
|
||
const w = mk(`mbl-${label}`)
|
||
const pr = modParse(TEXT(`mk-${label.replace(/[^\w-]/g, 'm')}`, data))
|
||
if (!pr.ok) return expect(pr.ok).toBe(true)
|
||
expect(w.installPlugin(modToPlugin(pr.pack)).ok).toBe(true)
|
||
expect(w.pluginList().some((p) => p.id === `mod-mk-${label.replace(/[^\w-]/g, 'm')}`)).toBe(true)
|
||
const bundle = w.exportModBundle()
|
||
expect(bundle).toContain(`mk-${label.replace(/[^\w-]/g, 'm')}`)
|
||
expect(w.removePlugin(`mod-mk-${label.replace(/[^\w-]/g, 'm')}`).ok).toBe(true)
|
||
expect(w.pluginList().some((p) => p.id === `mod-mk-${label.replace(/[^\w-]/g, 'm')}`)).toBe(false)
|
||
})
|
||
|
||
it.each([
|
||
['重装同包', 'dup-1'],
|
||
['重装同包2', 'dup-2'],
|
||
['重装同包3', 'dup-3']
|
||
])('%s:重复安装被拒(幂等不炸)', (label, id) => {
|
||
const w = mk(`di-${label}`)
|
||
const pr = modParse(TEXT(id))
|
||
if (!pr.ok) return expect(pr.ok).toBe(true)
|
||
const plug = modToPlugin(pr.pack)
|
||
expect(w.installPlugin(plug).ok).toBe(true)
|
||
expect(w.installPlugin(plug).ok).toBe(false)
|
||
})
|
||
|
||
it.each([
|
||
['合法', 'ev-safe-x', 'ev-other-name', true],
|
||
['撞核心', 'ev-legacypass', 'ev-x2', false],
|
||
['撞动态', 'ev-raid-x', 'ev-x3', false]
|
||
])('preflight %s:冲突矩阵', (_label, evId, uniqId, expectOk) => {
|
||
const w = mk(`pf-${_label}`)
|
||
const pr = modParse(TEXT(`m-${_label.replace(/[^\w-]/g, 'x')}`, { events: [{ id: evId, name: uniqId, category: 'daily', weight: 1, text: 't', options: [{ label: 'a', eff: {} }] }] }))
|
||
if (!pr.ok) return expect(pr.ok).toBe(true)
|
||
const r = modPreflight(w, pr.pack)
|
||
expect(r.ok).toBe(expectOk)
|
||
})
|
||
|
||
it.each(Array.from({ length: 10 }, (_, i) => [`seed-${i}`]))('持久化往返 %s:装→快照→重载→卸载', (seed) => {
|
||
const w = mk(`pp-${seed}`)
|
||
const pr = modParse(TEXT(`pp-${seed}`))
|
||
if (!pr.ok) return expect(pr.ok).toBe(true)
|
||
w.installPlugin(modToPlugin(pr.pack))
|
||
const snap = JSON.parse(JSON.stringify(w.state))
|
||
// 注册表工厂注册后 engineFromSnapshot(构造期读档真实重装)
|
||
registerPluginFactory(`mod-pp-${seed}`, () => modToPlugin(pr.pack as never) as never)
|
||
const e3 = engineFromSnapshot(snap as never)
|
||
expect(e3.world.pluginList().some((p) => p.id === `mod-pp-${seed}`)).toBe(true)
|
||
})
|
||
})
|
||
|
||
describe('矩阵:插件启停/能力卡', () => {
|
||
it.each([
|
||
['cap-1', 'demo-peaks', 'demo-guardian'],
|
||
['cap-2', 'demo-peaks', 'demo-guardian'],
|
||
['cap-3', 'demo-peaks', 'demo-guardian']
|
||
] as const)('%s:能力卡随启停联动', (seed, pid, capId) => {
|
||
const w = mk(`cg-${seed}`)
|
||
w.installPlugin(examplePlugin)
|
||
expect(w.sysEnabled(capId)).toBe(true)
|
||
const r = w.setPluginEnabled(pid, false)
|
||
if (r.ok) expect(w.sysEnabled(capId)).toBe(false)
|
||
else expect(w.sysEnabled(capId)).toBe(true) // 已装即启用
|
||
})
|
||
|
||
it.each([['k6'], ['k7'], ['k8']])('系统卡开关 %s:启停往返合法', (seed) => {
|
||
const w = mk(`ks-${seed}`)
|
||
expect(w.sysEnabled('production')).toBe(true)
|
||
const first = w.toggleSystem('production') // 返回切换后状态(false)
|
||
expect(w.sysEnabled('production')).toBe(first)
|
||
const second = w.toggleSystem('production')
|
||
expect(w.sysEnabled('production')).toBe(second)
|
||
expect(first).not.toBe(second)
|
||
})
|
||
})
|