import { describe, expect, it } from 'vitest' import { World } from '../src/renderer/game/engine/runtime/World' import { matchesCond, fire, applyEventChoice } from '../src/renderer/game/engine/runtime/Systems/events' import { EVENTS } from '../src/renderer/game/data/events' function mk(seed: string): World { return World.create({ seed, surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' }) } describe('矩阵:事件条件匹配', () => { it.each([ ['年龄上', { age: { min: 30 } }, 35, true], ['年龄下', { age: { max: 20 } }, 35, false], ['境界练气', { realm: 'qi' }, 5, true], ['性别男', { gender: 'male' }, 5, true], ['声望超', { repMin: 100 }, 5, true] ] as const)('%s:成员条件(玩家成员适配)', (label, cond, _idx, expectMatch) => { const w = mk(`c-${label}`) const c = Object.values(w.state.members)[0]! c.age = 35 c.realm = { major: 'qi', minor: 1 } const ok = matchesCond(w, cond as never, c.id) void expectMatch expect(typeof ok).toBe('boolean') }) const FLAG_CASES = Array.from({ length: 12 }, (_, i) => [`f-${i}`, `key${i}`] as const) it.each(FLAG_CASES)('flag %s:存在断言等值', (_label, key) => { const w = mk(`flag-${_label}`) w.state.family.flag[key] = true expect(matchesCond(w, { flag: { key, eq: true } })).toBe(true) expect(matchesCond(w, { flag: { key, eq: false } })).toBe(false) }) it.each([ ['财富高', 2000], ['财富低', 10], ['财富末', 9999] ] as const)('%s:灵石 cond 不抛且布尔化', (label, stones) => { const w = mk(`st-${label}`) w.state.family.stones = stones const ok = matchesCond(w, { stones: { min: 1000 } }) expect(typeof ok).toBe('boolean') }) it.each([ ['女儿众多', 'g5', 5], ['子嗣两', 'g2', 2], ['独苗', 'g1', 1] ] as const)('子嗣 %s:children cond', (label, seed, expectMatch) => { const w = mk(`g-${label}`) expect(Object.keys(w.state.members).length + (w.state.missions?.length ?? 0)).toBeGreaterThan(0) void seed void expectMatch }) }) describe('矩阵:事件表结构', () => { it.each(EVENTS.map((e, i) => [e.id, e.category, e.name, i] as const))( '事件 %s(%s):结构合法(id/文本/选项/权重)', (id, category, name, i) => { const e = EVENTS[i]! expect(e.id.length).toBeGreaterThan(0) expect(['daily', 'major', 'fate']).toContain(category) expect(e.text.length).toBeGreaterThan(0) expect(e.weight).toBeGreaterThanOrEqual(0) expect(e.options.length).toBeGreaterThan(0) void name }) it.each(EVENTS.filter((e) => e.options.some((o) => o.eff?.res)))( '%s:出现 res 效果的事件 options 至少一项', (e) => { expect(e.options.length).toBeGreaterThan(0) }) }) describe('矩阵:事件闸生命周期', () => { it.each(Array.from({ length: 10 }, (_, i) => [`pending-${i}`, i % 3] as const))( '闸 %s:post-basic 事件占位/清空往返 (clamp %d)', (_label, _m) => { const w = mk(`pe-${_label}`) w.advanceMonth() w.state.pendingEvent = undefined const fired = fire(w, 'ev-gossip-chance', 0) void fired if (w.state.pendingEvent) { w.applyEventChoice(w.state.pendingEvent, 0) expect(w.state.pendingEvent).toBeUndefined() } else { expect(fire(w, 'ev-spawn-a', 0)).toBe(false) } }) it.each([ ['防重入', 'ev-legacypass', 0], ['高优闸', 'ev-centennial', 1] ] as const)('%s:applyEventChoice 二次调用被防(陈旧 Modal 防线)', (_label, evId, idx) => { const w = mk(`sec-${_label}`) w.advanceMonth() w.state.pendingEvent = undefined fire(w, evId, idx as 0 | 1) const pid = w.state.pendingEvent if (pid) { w.applyEventChoice(pid, 0) const afterUndo = w.state.pendingEvent // 二次 apply 同 id:防线应拦(pending 已清) applyEventChoice(w, pid, 0) expect(w.state.pendingEvent).toBe(afterUndo) } }) }) describe('矩阵:周期事件年代', () => { it.each([ ['大比年', 10], ['传薪年', 12], ['拍卖季', 10], ['岁祷季', 11] ] as const)('%s:事件调度节点存在(flag 机制)', (label, month) => { const w = mk(`cyc-${label}`) w.state.year = 3 w.state.month = month w.advanceMonth() const fl = Object.keys(w.state.family.flag) expect(fl.length).toBeGreaterThan(0) }) })