Files
ChronicleOfTheImmortalClan/tests/event-state-matrix.test.ts
T
thzxx 3195f67455 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 零漂移)
2026-08-23 10:04:33 +08:00

118 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()
})
})