Files
ChronicleOfTheImmortalClan/tests/event-state-matrix.test.ts
T
thzxx ff6df8054c refactor(0.1.14-P1): 内核归位——game/ 按引擎架构重排(零行为漂移)
结构(旧 game/core+engine → 新 engine/ 域):
- engine/kernel/  时钟/随机/插件协议/fxqueue/timesense/format/urgency/guide/names(原 core)
- engine/narrative/  legacy/报告/列传/谱系/年轴(原 core 叙事族)
- engine/runtime/   World/creation/pcgen/ApiFacade/capabilities/pluginManager/boot/clocks + Systems/*(12 系统)
- engine/sim/     Market(未来 WorldSim 同行)
- 旧 game/core、engine/systems、engine/world.ts 等路径全部废弃(无 re-export 兼容层)

验证:35 套件/967 测试全绿(金钟罩三档零漂移=纯搬迁无行为变化)
typecheck 0 error
2026-08-23 13:23:25 +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/runtime/World'
import { EVENTS, EffectDef } from '../src/renderer/game/data/events'
import { applyEventChoice } from '../src/renderer/game/engine/runtime/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()
})
})