test: 测试矩阵扩至 161 项(深度审计 + 5 处缺陷修复)
新增测试矩阵(12 文件 / 161 cases): - rng 12 / data 18 / pcgen+cultivation 20 / economy 12 / combat+missions 24 - marriage+diplomacy 13 / events 12 / world+lifecycle 8(含镜像一致)/ save 4+5 / audit 8 审计带出并修复的真实缺陷: 1. Rng.pick 空数组未报错(防空白数组进而崩溃难查) 2. combatPowerOf 未应用功法悟道品阶战力加成(0.1.2 设计缺口) 3. 重伤者即使不代表伤状态仍按满速修炼(health 进修炼公式) 4. 寡妇/鳏夫无法再婚:候选列表放着人执行层却拒婚(spouseId 死旧契约) 5. isWidowed 参数契约陷阱:传 id 静默 false(升级接受 Character|Id 双签名)
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { World } from '../src/renderer/game/engine/world'
|
||||
import { matchesCond, applyEventChoice, findEvent, eventRoll } from '../src/renderer/game/engine/systems/events'
|
||||
import { EVENTS } from '../src/renderer/game/data/events'
|
||||
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('event 条件矩阵', () => {
|
||||
it('all/any/not 逻辑嵌套', () => {
|
||||
const w = baseWorld('ev-m1')
|
||||
const cond = { all: [{ minAdult: 2 }, { any: [{ minRep: 0 }, { maxRep: -1 }] }], not: { minBuilding: { id: 'danfang', level: 1 } } }
|
||||
expect(matchesCond(w, cond)).toBe(true)
|
||||
const bad = { all: [{ minAdult: 2 }], not: { minAdult: 1 } }
|
||||
expect(matchesCond(w, bad)).toBe(false)
|
||||
})
|
||||
|
||||
it('数值条件边界(>=/<)', () => {
|
||||
const w = baseWorld('ev-m2')
|
||||
w.state.family.reputation = 30
|
||||
expect(matchesCond(w, { minRep: 30 })).toBe(true)
|
||||
expect(matchesCond(w, { minRep: 31 })).toBe(false)
|
||||
expect(matchesCond(w, { maxRep: 30 })).toBe(true)
|
||||
expect(matchesCond(w, { maxRep: 29 })).toBe(false)
|
||||
})
|
||||
|
||||
it('资源与建筑条件', () => {
|
||||
const w = baseWorld('ev-m3')
|
||||
w.state.family.inventory['lingcao'] = 100
|
||||
expect(matchesCond(w, { minResource: { id: 'lingcao', n: 50 } })).toBe(true)
|
||||
expect(matchesCond(w, { minResource: { id: 'lingcao', n: 200 } })).toBe(false)
|
||||
w.state.family.buildings = { danfang: 2 }
|
||||
expect(matchesCond(w, { minBuilding: { id: 'danfang', level: 2 } })).toBe(true)
|
||||
expect(matchesCond(w, { minBuilding: { id: 'danfang', level: 3 } })).toBe(false)
|
||||
expect(matchesCond(w, { minBuilding: { id: 'lingshou', level: 1 } })).toBe(false)
|
||||
})
|
||||
|
||||
it('关系条件和 flag 条件', () => {
|
||||
const w = baseWorld('ev-m4')
|
||||
const npc = w.state.npcFamilies['n-nulei']
|
||||
npc.relation = -60
|
||||
expect(matchesCond(w, { relation: { npcId: 'n-nulei', lt: -50 } })).toBe(true)
|
||||
expect(matchesCond(w, { relation: { npcId: 'n-nulei', gt: 0 } })).toBe(false)
|
||||
w.state.family.flag['tenants'] = true
|
||||
expect(matchesCond(w, { flag: { key: 'tenants', eq: true } })).toBe(true)
|
||||
expect(matchesCond(w, { flag: { key: 'tenants', eq: false } })).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('event 生命周期', () => {
|
||||
it('once 事件只触发一次', () => {
|
||||
const w = baseWorld('ev-o1')
|
||||
const onceEv = EVENTS.find((e) => e.once)!
|
||||
w.state.completedEvents = [onceEv.id]
|
||||
const before = w.state.completedEvents.length
|
||||
applyEventChoice(w, onceEv.id, 0)
|
||||
// applyEventChoice 会重复检查 completedEvents; actually fire 由 eventRoll 把关;这里验证 once 不重入:
|
||||
expect(w.state.completedEvents.length).toBe(before) // 已存在不重复 push
|
||||
})
|
||||
|
||||
it('applyChoice 未知事件安全退出并清 pending', () => {
|
||||
const w = baseWorld('ev-o2')
|
||||
w.state.pendingEvent = 'ev-not-exist'
|
||||
applyEventChoice(w, 'ev-not-exist', 0)
|
||||
expect(w.state.pendingEvent).toBeUndefined()
|
||||
})
|
||||
|
||||
it('选择应用资源/声誉/关系效果并明确记录', () => {
|
||||
const w = baseWorld('ev-o3')
|
||||
const ev = EVENTS.find((e) => e.id === 'ev-youdao')! // 灵草-5 聚气丹+1
|
||||
w.state.family.inventory['lingcao'] = 50
|
||||
const p0 = w.state.family.inventory['pill-qiyuan'] ?? 0
|
||||
applyEventChoice(w, ev.id, 0)
|
||||
expect(w.state.family.inventory['lingcao']).toBe(45)
|
||||
expect(w.state.family.inventory['pill-qiyuan']).toBe(p0 + 1)
|
||||
})
|
||||
|
||||
it('raid 选项触发实际战斗记录与冷却', () => {
|
||||
const w = baseWorld('ev-o4')
|
||||
const npc = w.state.npcFamilies['n-nulei']
|
||||
npc.relation = -80
|
||||
const b0 = w.state.battles.length
|
||||
applyEventChoice(w, 'ev-raid-n-nulei', 0)
|
||||
expect(w.state.battles.length).toBeGreaterThan(b0)
|
||||
expect(w.state.family.flag['raidCD-n-nulei']).toBe(w.state.year)
|
||||
})
|
||||
|
||||
it('事件效果不会把成员数削减到非法', () => {
|
||||
const w = baseWorld('ev-o5')
|
||||
for (const c of Object.values(w.state.members)) {
|
||||
if (c.id !== 'x1') c.alive = false
|
||||
}
|
||||
const mortal = w.state.members['x1']
|
||||
mortal.realm = { major: 'spirit', minor: 0 }
|
||||
applyEventChoice(w, 'ev-feisheng', 0)
|
||||
// stay 分支:成员仍在世,获得仙风特质
|
||||
expect(w.state.members['x1'].alive).toBe(true)
|
||||
expect(w.state.members['x1'].traits).toContain('fengxian')
|
||||
})
|
||||
|
||||
it('飞升离开分支:族内蒙荫与头衔交接', () => {
|
||||
const w = baseWorld('ev-o6')
|
||||
const strong = w.state.members['x4']
|
||||
strong.realm = { major: 'spirit', minor: 0 }
|
||||
applyEventChoice(w, 'ev-feisheng', 1)
|
||||
expect(strong.alive).toBe(false)
|
||||
expect(strong.deathCause).toBe('云游飞升')
|
||||
expect(w.state.family.flag['fengFeiBless']).toBe(true)
|
||||
// 若继任者存在
|
||||
expect(w.state.family.headId).toBeTruthy()
|
||||
})
|
||||
|
||||
it('百年庆典仅在第 100 年起火', () => {
|
||||
const w = baseWorld('ev-o7')
|
||||
w.state.year = 99
|
||||
w.state.month = 12
|
||||
w.advanceMonth()
|
||||
expect(w.state.pendingEvent).toBe('ev-centennial')
|
||||
// 已完成后不再重复(completedEvents 注入)
|
||||
const w2 = baseWorld('ev-o8')
|
||||
w2.state.year = 100
|
||||
w2.state.month = 6
|
||||
w2.state.completedEvents = ['ev-centennial']
|
||||
for (let i = 0; i < 8; i++) w2.advanceMonth()
|
||||
expect(w2.state.pendingEvent).not.toBe('ev-centennial')
|
||||
})
|
||||
|
||||
it('eventRoll 不投出已完成的 once 事件', () => {
|
||||
const w = baseWorld('ev-o9')
|
||||
for (const e of EVENTS.filter((x) => x.once)) w.state.completedEvents.push(e.id)
|
||||
for (let i = 0; i < 40; i++) {
|
||||
w.advanceMonth()
|
||||
w.state.pendingEvent = undefined
|
||||
}
|
||||
const fired = w.state.completedEvents.length === EVENTS.filter((x) => x.once).length
|
||||
expect(fired).toBe(true)
|
||||
// 没有任何 once 事件被重复发起: 通过 completedEvents 长度恒定同初值
|
||||
expect(w.state.chronicle.length).toBeLessThan(40) // 有但不多
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user