import { describe, expect, it } from 'vitest' import { World } from '../src/renderer/game/engine/runtime/World' import { applyEventChoice } from '../src/renderer/game/engine/runtime/Systems/events' import { resetSaveBus, attachLogSink } from './world.helpers' const SEEDS = ['long-a', 'long-b', 'long-c'] function runYearWithActions(w: World, years: number): void { const months = years * 12 for (let i = 0; i < months; i++) { if (w.state.gameOver) break if (w.state.pendingEvent) { applyEventChoice(w, w.state.pendingEvent, 0) w.state.pendingEvent = undefined } w.advanceMonth() } } function highestMajor(w: World): number { const order = ['mortal', 'qi', 'foundation', 'core', 'nascent', 'spirit'] let max = 0 for (const c of Object.values(w.state.members)) { if (!c.alive) continue const idx = order.indexOf(c.realm.major) if (idx > max) max = idx } return max } describe('长线存活验收(玩家代理)', () => { it.each(SEEDS.map((s) => [s] as const))('%s:200 年内未因修炼瓶颈灭族', (seed) => { const w = World.create({ seed, surname: '岳', familyName: '岳家', motto: 'm', difficulty: 'normal' }) resetSaveBus() attachLogSink(w) runYearWithActions(w, 200) const alive = Object.values(w.state.members).filter((c) => c.alive).length // 若灭族,必须发生在非常晚期(>120Y),且修炼已达高位——即非"卡死" if (w.state.gameOver) { expect(w.state.gameOver.year).toBeGreaterThan(120) } expect(alive).toBeGreaterThanOrEqual(0) }) it.each(SEEDS.map((s) => [s] as const))('%s:有成员达到筑基或以上(修炼不卡死)', (seed) => { const w = World.create({ seed, surname: '岳', familyName: '岳家', motto: 'm', difficulty: 'normal' }) resetSaveBus() attachLogSink(w) runYearWithActions(w, 120) expect(highestMajor(w)).toBeGreaterThanOrEqual(2) }) it('玩家代理 300 年内不卡在炼气(存在高境界成员或已登高位后陨落)', () => { const w = World.create({ seed: 'long-g', surname: '岳', familyName: '岳家', motto: 'm', difficulty: 'normal' }) resetSaveBus() attachLogSink(w) runYearWithActions(w, 300) const reached = w.state.chronicle.some((e) => e.category === 'breakthrough' && /筑基|金丹|元婴|化神/.test(e.text)) if (w.state.gameOver) { expect(reached).toBe(true) // 若灭族,必曾登高位——非修炼卡死 } else { expect(highestMajor(w)).toBeGreaterThanOrEqual(1) } }) }) describe('渡劫悬置回退', () => { it('悬置 12 月自动压制(挂机不软锁)', () => { const w = World.create({ seed: 'trib-hang', surname: '袁', familyName: '袁家', motto: 'm', difficulty: 'normal' }) resetSaveBus() attachLogSink(w) const c = w.state.members['x5'] c.realm = { major: 'qi', minor: 8 } c.realmProgress = 100 c.mind = 9 c.perception = 9 let suppressedOnce = false for (let i = 0; i < 30; i++) { w.advanceMonth() // 压制发生时事件被清 & delay 被设 if (!w.state.pendingEvent && c.tribDelayYear && (c.tribDelayYear ?? 0) >= 1 && !c.tribPendingMonths) suppressedOnce = true if (c.tribDelayYear === undefined && !c.tribPendingMonths && !w.state.pendingEvent?.startsWith('ev-trib-')) { // 可能刚 fire 于本月 } } // 至少发生过一次压制(不软锁) expect(suppressedOnce).toBe(true) }) it('玩家处理渡劫后确实晋升(qi8→筑基)', () => { const w = World.create({ seed: 'trib-handle', surname: '袁', familyName: '袁家', motto: 'm', difficulty: 'normal' }) resetSaveBus() attachLogSink(w) const c = w.state.members['x5'] c.realm = { major: 'qi', minor: 8 } c.realmProgress = 100 c.mind = 9 c.perception = 9 for (let i = 0; i < 60; i++) { if (w.state.pendingEvent) { applyEventChoice(w, w.state.pendingEvent, 0) // 选"硬渡" w.state.pendingEvent = undefined } w.advanceMonth() if (c.realm.major === 'foundation') break } expect(c.realm.major).toBe('foundation') }) }) describe('拍卖与传薪', () => { it('拍卖三分支统一不破', () => { for (let i = 0; i < 10; i++) { const w = World.create({ seed: `auc-${i}`, surname: '封', familyName: '封家', motto: 'm', difficulty: 'normal' }) resetSaveBus() attachLogSink(w) applyEventChoice(w, 'ev-auction', 0) expect(w.state.family.stones).toBeGreaterThanOrEqual(500) } }) it('名宿传薪事件可触发', () => { const w = World.create({ seed: 'legacy-x', surname: '危', familyName: '危家', motto: 'm', difficulty: 'normal' }) w.state.year = 12 const c = w.state.members['x1'] c.bornYear = 1 - 70 c.realm = { major: 'foundation', minor: 1 } let fired = false for (let i = 0; i < 48; i++) { if (w.state.pendingEvent === 'ev-legacypass') { fired = true break } if (w.state.pendingEvent) w.state.pendingEvent = undefined w.advanceMonth() } expect(fired).toBe(true) }) })