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,117 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { World, normalizeGameState } from '../src/renderer/game/engine/world'
|
||||
import { EVENTS, EffectDef } from '../src/renderer/game/data/events'
|
||||
import { applyEventChoice } from '../src/renderer/game/engine/systems/events'
|
||||
import { POSTS } from '../src/renderer/game/data/posts'
|
||||
import { ASPIRATIONS } from '../src/renderer/game/data/aspirations'
|
||||
import { resetSaveBus, attachLogSink } from './world.helpers'
|
||||
|
||||
function baseWorld(seed: string): World {
|
||||
const w = World.create({ seed, surname: '萧', familyName: '萧家', motto: 'm', difficulty: 'normal' })
|
||||
resetSaveBus()
|
||||
attachLogSink(w)
|
||||
return w
|
||||
}
|
||||
|
||||
describe('事件选项应用矩阵(逐事件逐选项)', () => {
|
||||
it.each(EVENTS.flatMap((e) => e.options.map((o, idx) => [e.id, idx, o.label] as const)))(
|
||||
'事件 %s 选%i(%s)应用后世界不破',
|
||||
(id, idx) => {
|
||||
const w = baseWorld('ev-app-' + id)
|
||||
applyEventChoice(w, id, idx)
|
||||
expect(w.state.family.stones).toBeGreaterThanOrEqual(0)
|
||||
for (const inv of Object.values(w.state.family.inventory)) {
|
||||
if (typeof inv === 'number') expect(inv).toBeGreaterThanOrEqual(0)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('effect 结构合法矩阵', () => {
|
||||
it.each(EVENTS.flatMap((e) => e.options.map((o) => [e.id, o.eff] as const)))('事件 %s 效果可回放', (id, eff) => {
|
||||
expect(typeof eff).toBe('object')
|
||||
expect(eff).not.toBeNull()
|
||||
})
|
||||
|
||||
it.each(EVENTS.flatMap((e) => e.options.map((o, i) => [e.id, i, o.eff.res] as const)).filter(([, , r]) => !!r))(
|
||||
'事件 %s 资源效果引用存在',
|
||||
(_id, _i, res) => {
|
||||
for (const [k] of Object.entries(res!)) {
|
||||
expect(k === 'stones' || ['lingcao', 'lingkuang', 'beastcore'].includes(k) || k.startsWith('pill-') || k.startsWith('weapon-')).toBe(true)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('职事能力矩阵', () => {
|
||||
// head 由继承链独占(assignPost 拒绝),跳过
|
||||
const assignable = Object.entries(POSTS).filter(([id]) => id !== 'head')
|
||||
it.each(assignable.map(([id, def]) => [id, def.max] as const))('职事 %s 上限可重复指派', (id, max) => {
|
||||
const w = baseWorld('post-' + id)
|
||||
let assigned = 0
|
||||
for (const c of Object.values(w.state.members)) {
|
||||
if (assigned >= max) break
|
||||
if (w.assignPost(c.id, id)) assigned++
|
||||
}
|
||||
expect(assigned).toBe(max)
|
||||
})
|
||||
|
||||
it.each(assignable.map(([id]) => [id] as const))('职事 %s 不可指派寄读者', (id) => {
|
||||
const w = baseWorld('post-ap-' + id)
|
||||
const c = w.state.members['x3']
|
||||
c.state = 'apprentice'
|
||||
expect(w.assignPost(c.id, id)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('志向矩阵', () => {
|
||||
it.each(Object.keys(ASPIRATIONS).map((id) => [id] as const))('志向 %s 生效于世界(无异常)', (id) => {
|
||||
const w = baseWorld('asp-' + id)
|
||||
for (const c of Object.values(w.state.members)) c.aspiration = id
|
||||
w.advanceMonth()
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('normalizeGameState 全残缺矩阵', () => {
|
||||
it.each([
|
||||
['finance', 'yearStats', 'yearlyReports', 'stats'].map((k) => [k] as const)
|
||||
].flat())('%s 缺失补齐', (key) => {
|
||||
const w = baseWorld('norm-' + key)
|
||||
const state = JSON.parse(JSON.stringify(w.state))
|
||||
delete (state as Record<string, unknown>)[key]
|
||||
const fixed = normalizeGameState(state)
|
||||
expect((fixed as Record<string, unknown>)[key]).toBeTruthy()
|
||||
})
|
||||
|
||||
it('无 headId 老档补齐首个存活着', () => {
|
||||
const w = baseWorld('norm-head')
|
||||
const state = JSON.parse(JSON.stringify(w.state))
|
||||
state.family.headId = 'x-not-exist'
|
||||
const fixed = normalizeGameState(state)
|
||||
expect(fixed.family.headId).toBe('x1')
|
||||
})
|
||||
|
||||
it('缺 npcFamilies 补齐空表(不崩外交)', () => {
|
||||
const w = baseWorld('norm-npc')
|
||||
const state = JSON.parse(JSON.stringify(w.state))
|
||||
delete state.npcFamilies
|
||||
const fixed = normalizeGameState(state)
|
||||
const w2 = new World(fixed)
|
||||
for (let i = 0; i < 3; i++) w2.advanceMonth() // 不抛
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('大比寄读渡劫动态事件矩阵', () => {
|
||||
it.each([
|
||||
['ev-tournament-10', '称病不出'],
|
||||
['ev-recruit', '婉拒'],
|
||||
['ev-centennial', '阖家简庆']
|
||||
] as const)('%s 选「%s」无副作用', (id, _label) => {
|
||||
const w = baseWorld('dyn-' + id.slice(0, 6))
|
||||
const optIdx = 0 // 第一选项最简
|
||||
applyEventChoice(w, id, optIdx)
|
||||
expect(w.state.pendingEvent).toBeUndefined()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user