import { describe, expect, it } from 'vitest' import { World } from '../src/renderer/game/engine/runtime/World' import { applyEventChoice, applyEffect } from '../src/renderer/game/engine/runtime/Systems/events' import { EffectDef } from '../src/renderer/game/data/events' import { Character } from '../src/renderer/game/types/domain' 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 } function applyEff(w: World, eff: EffectDef): void { applyEffect(w, eff) } describe('memberBy 效果全矩阵(核心系统)', () => { it('exp/wound/heal 对全目标执行', () => { const w = baseWorld('mb-a') const before = Object.values(w.state.members).map((c) => c.realmProgress) applyEventChoice(w, 'ev-youdao', 0) // 已测 applyEventChoice(w, 'ev-yupei', 2) // 幼女 loot const after = Object.values(w.state.members).map((c) => c.realmProgress) void before void after expect(w.state.members['x5'].fortune).toBeGreaterThan(7) // loot +2 到基础 5-6 }) it('inspire 全部族裔修为小幅增长', () => { const w = baseWorld('mb-b') // 构造 inspire 全族:手写应用 const alive = w.aliveMembers() const before = alive.map((c) => c.realmProgress) applyEff(w, { memberBy: { by: 'inspire', target: 'all', n: 5 } }) const after = w.aliveMembers().map((c) => c.realmProgress) expect(after.filter((v, i) => v !== before[i]).length).toBeGreaterThan(0) expect(w.state.members['x3'].realmProgress).toBeLessThanOrEqual(100) }) it('fatal 分支:全部成员无差影响或阵亡(不抛错)', () => { const w = baseWorld('mb-c') applyEff(w, { memberBy: { by: 'wound', target: 'random', n: 30 } }) const fresh = w.aliveMembers() expect(fresh.length).toBeGreaterThan(0) applyEff(w, { memberBy: { by: 'heal', target: 'all', n: 50 } }) for (const c of Object.values(w.state.members)) { expect(c.health).toBeGreaterThanOrEqual(0) expect(c.health).toBeLessThanOrEqual(100) } }) it('highestPerception/highestPower/highestFortune 目标稳定命中', () => { const w = baseWorld('mb-d') const p0 = w.aliveMembers()[0] void p0 applyEff(w, { memberBy: { by: 'exp', target: 'highestPerception', n: 2 } }) applyEff(w, { memberBy: { by: 'loot', target: 'highestFortune', n: 1 } }) applyEff(w, { memberBy: { by: 'heal', target: 'highestPower', n: 10 } }) expect(w.state.members['x4'].health).toBeGreaterThanOrEqual(100) // 最高战力者(x4/叔父)已治满 }) it('madness/genius 变格不越界', () => { const w = baseWorld('mb-e') applyEff(w, { memberBy: { by: 'madness', target: 'oldest', n: 1 } }) applyEff(w, { memberBy: { by: 'madness', target: 'random', n: 1 } }) applyEff(w, { memberBy: { by: 'genius', target: 'random', n: 1 } }) for (const c of Object.values(w.state.members)) { expect(c.perception).toBeLessThanOrEqual(10) expect(c.mind).toBeGreaterThanOrEqual(1) } }) }) describe('advance 死局与时钟守卫', () => { it('全员死亡后 clock 仍安全步进(生产与收尾仍跑)', () => { const w = baseWorld('dead-a') for (const c of Object.values(w.state.members)) c.alive = false w.advanceMonth() expect(w.state.gameOver).toBeTruthy() w.advanceMonth() expect(w.state.year).toBeGreaterThanOrEqual(1) expect(w.state.family.stones).toBeGreaterThanOrEqual(0) }) it('头衔无人可继时 gameOver 语义完整', () => { const w = baseWorld('dead-b') for (const c of Object.values(w.state.members)) c.alive = false w.advanceMonth() expect(w.state.gameOver?.reason).toContain('香火') expect(w.state.gameOver?.year).toBeGreaterThanOrEqual(1) }) it('继承人仅存女性也可继位(家谱连续性)', () => { const w = baseWorld('dead-c') const x1 = w.state.members['x1'] x1.alive = false x1.deathYear = w.state.year const x3 = w.state.members['x3'] x3.alive = false x3.deathYear = w.state.year w.advanceMonth() expect(['x2', 'x5']).toContain(w.state.family.headId) }) }) describe('事件效果残留防御', () => { it('关系 clamp 至 ±100', () => { const w = baseWorld('rm-a') applyEventChoice(w, 'ev-raid-n-nulei', 0) // 关系打向正 25? 取决于胜负 applyEventChoice(w, 'ev-zhusu', 2) // -20 for (const n of Object.values(w.state.npcFamilies)) { expect(n.relation).toBeGreaterThanOrEqual(-100) expect(n.relation).toBeLessThanOrEqual(100) } }) it('inventory 不为负(全部 effect 协同)', () => { const w = baseWorld('rm-b') w.state.family.inventory['lingcao'] = 6 applyEventChoice(w, 'ev-youdao', 0) // -5 expect(w.state.family.inventory['lingcao']).toBeGreaterThanOrEqual(0) }) })