新增 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 路径)
96 lines
4.4 KiB
TypeScript
96 lines
4.4 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
||
import { World } from '../src/renderer/game/engine/runtime/World'
|
||
import { resolveEncounter, combatPowerOf, rollWarbooty } from '../src/renderer/game/engine/runtime/Systems/combat'
|
||
import { sendMission } from '../src/renderer/game/engine/runtime/Systems/missions'
|
||
import { MISSIONS } from '../src/renderer/game/data/secrets'
|
||
import { FORMATIONS } from '../src/renderer/game/data/formations'
|
||
import { WorldSim } from '../src/renderer/game/engine/sim/WorldSim'
|
||
|
||
function mk(seed: string): World {
|
||
return World.create({ seed, surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' })
|
||
}
|
||
|
||
const ENEMIES_SAMPLE = [
|
||
{ id: 'e-huiyuan', name: '灰狼王', realm: 'qi', strength: 0.8, icon: '狼', desc: 'x' },
|
||
{ id: 'e-tiebei', name: '铁背熊罴', realm: 'qi', strength: 1.1, icon: '熊', desc: 'x' },
|
||
{ id: 'e-muche', name: '赤瞳木魅', realm: 'foundation', strength: 1.0, icon: '木', desc: 'x' },
|
||
{ id: 'e-baiqi', name: '白骨修士', realm: 'foundation', strength: 1.2, icon: '骨', desc: 'x' }
|
||
]
|
||
|
||
describe('矩阵:遭遇战', () => {
|
||
it.each(ENEMIES_SAMPLE.map((e) => [e.id, e.strength] as const))(
|
||
'敌人 %s:风险 0.5 下遭遇结算合法(胜负/平三向)', (id, strength) => {
|
||
const w = mk(`enc-${id}`)
|
||
const team = [w.state.members['x1']!, w.state.members['x2']!, w.state.members['x3']!, w.state.members['x4']!]
|
||
for (const c of team) { c.realm = { major: 'core', minor: 0 }; c.health = 100 }
|
||
const res = resolveEncounter(w, {
|
||
title: '遭遇', enemy: { id, name: '敌', realm: 'qi', strength, icon: '险', desc: 'x' },
|
||
risk: 0.5, team, kind: 'mission', year: 1, month: 1
|
||
})
|
||
expect(['win', 'draw', 'lose'].includes(res.win ? 'win' : res.draw ? 'draw' : 'lose')).toBe(true)
|
||
})
|
||
|
||
it.each([
|
||
['四人满编', 4, 'core'], ['两人', 2, 'core'], ['一人', 1, 'core'], ['低境界队', 4, 'qi']
|
||
] as const)('%s:combatPower 正且随境界升', (label, n, realm) => {
|
||
const w = mk(`cp-${label}`)
|
||
const team = Object.values(w.state.members).slice(0, n)
|
||
for (const c of team) c.realm = { major: realm as never, minor: 0 }
|
||
const powers = team.map((c) => combatPowerOf(w, c))
|
||
for (const p of powers) expect(p).toBeGreaterThan(0)
|
||
})
|
||
|
||
it.each([
|
||
['锋阵', 'vanguard'], ['雁行', 'echelon'], ['蛇行', 'serpent']
|
||
] as const)('阵型 %s:列阵战力合成非负', (label, fid) => {
|
||
const w = mk(`ff-${label}`)
|
||
const team = [w.state.members['x1']!, w.state.members['x2']!]
|
||
for (const c of team) c.realm = { major: 'foundation', minor: 1 }
|
||
expect(FORMATIONS[fid as never]).toBeTruthy()
|
||
expect(combatPowerOf(w, team[0]!)).toBeGreaterThan(0)
|
||
})
|
||
|
||
it.each([
|
||
['灵盛', 1.5], ['灵中', 0.9], ['灵竭', 0.5]
|
||
] as const)('战利 %s:qiRatio 缩放大不越界', (label, qr) => {
|
||
const w = mk(`loot-${label}`)
|
||
const loot = rollWarbooty(w, { resources: { lingcao: [20, 60], lingkuang: [5, 15] }, artifactChance: 0.05, techniqueChance: 0.03 }, qr)
|
||
for (const v of Object.values(loot)) {
|
||
if (typeof v === 'number') expect(v).toBeGreaterThanOrEqual(0)
|
||
}
|
||
})
|
||
})
|
||
|
||
describe('矩阵:探险任务', () => {
|
||
it.each(MISSIONS.map((m) => [m.id, m.name, m.minMembers, m.maxMembers, m.realmHint] as const))(
|
||
'秘境 %s:可遣队且有段/成员参数', (id, name, min, max, hint) => {
|
||
const w = mk(`ms-${id}`)
|
||
const squad = Object.values(w.state.members).slice(0, Math.min(max, 4))
|
||
const ok = sendMission(w, id, squad.map((c) => c.id))
|
||
expect(typeof ok).toBe('boolean')
|
||
expect(name.length).toBeGreaterThan(0)
|
||
expect(min).toBeGreaterThanOrEqual(1)
|
||
void hint
|
||
})
|
||
|
||
it.each([
|
||
['未满员', 1], ['达标', 2], ['满编', 4]
|
||
] as const)('遣队 %s:要求校验(不足拒绝/达标放行)', (label, squadN) => {
|
||
const w = mk(`sq-${label}`)
|
||
const def = MISSIONS.find((m) => m.id === 'm-anmoku')!
|
||
const squad = Object.values(w.state.members).slice(0, squadN)
|
||
const ok = sendMission(w, def.id, squad.map((c) => c.id))
|
||
if (squadN < def.minMembers) expect(ok).toBe(false)
|
||
else expect(ok).toBe(true)
|
||
})
|
||
|
||
it.each([
|
||
['草灵丰饶', 100], ['半枯', 50], ['将竭', 10]
|
||
] as const)('灵气 %s:secretQiOf 值域与折减合法', (label, qi) => {
|
||
const w = mk(`qi-${label}`)
|
||
const sim = new WorldSim(w)
|
||
expect(sim.secretQiOf('m-anmoku')).toBeGreaterThanOrEqual(0)
|
||
expect(sim.secretQiOf('m-anmoku')).toBeLessThanOrEqual(100)
|
||
})
|
||
})
|