import { describe, expect, it } from 'vitest' import { World } from '../src/renderer/game/engine/world' import { computeLegacy, resolveLegacy, peakRealmIndex, grandTechniqueCount } from '../src/renderer/game/core/legacy' import { runTournament, buildApprentice, finalizeApprentice, apprenticeCandidates } from '../src/renderer/game/engine/systems/tournament' import { applyEventChoice } from '../src/renderer/game/engine/systems/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('legacy 望气评分', () => { it('四维分随面板数据单调增(声望峰)', () => { const w = baseWorld('leg-s1') const d0 = computeLegacy(w.state) w.state.stats.repPeak = 78 const d1 = computeLegacy(w.state) expect(d1.weiMing).toBeGreaterThan(d0.weiMing) }) it('peakRealmIndex 与 grandTechniqueCount 计算正确', () => { const w = baseWorld('leg-s2') w.state.members['x4'].realm = { major: 'core', minor: 0 } expect(peakRealmIndex(w.state.members)).toBe(30) w.state.members['x3'].techniqueRank = 2 expect(grandTechniqueCount(w.state.members)).toBe(1) }) it('十二称号分档边界正确', () => { const w = baseWorld('leg-s3') w.state.stats.feishengCount = 1 expect(resolveLegacy(w.state).title).toBe('飞升之资') w.state.stats.feishengCount = 0 // 强制高分档 150+ w.state.stats.repPeak = 80 w.state.stats.maxRealmIdx = 60 // 化神 w.state.stats.popPeak = 45 w.state.stats.techniqueGrand = 12 w.state.year = 250 const arch = resolveLegacy(w.state) expect(['仙朝遗脉', '中州望族']).toContain(arch.title) }) it('极弱家族落入冢中枯骨档', () => { const w = baseWorld('leg-s4') w.state.stats.repPeak = 0 w.state.stats.maxRealmIdx = -1 w.state.stats.popPeak = 1 w.state.year = 3 w.state.stats.feishengCount = 0 expect(resolveLegacy(w.state).title).toBe('冢中枯骨') }) it('定局落印单次生效且写入史书', () => { const w = baseWorld('leg-s5') const c0 = w.state.chronicle.length const arch = w.resolveLegacyNow() expect(arch.title).toBeTruthy() expect(w.state.family.flag['resolved']).toBe(true) expect(w.state.stats.resolvedYear).toBe(w.state.year) expect(w.state.stats.resolveTitle).toBe(arch.title) expect(w.state.chronicle.length).toBe(c0 + 1) }) }) describe('tournament 太虚大比', () => { it('十年(year%5=0 & year>=10)触发征召事件', () => { const w = baseWorld('tr-a') w.state.year = 9 w.state.month = 12 w.advanceMonth() expect(w.state.pendingEvent).toBe('ev-tournament-10') w.state.pendingEvent = undefined w.advanceMonth() expect(w.state.pendingEvent).toBe('ev-tournament-10') // 已火后完成列表保护内一月再次 fire // 完成记入后不再 w.state.completedEvents.push('ev-tournament-10') w.state.pendingEvent = undefined w.advanceMonth() expect(w.state.pendingEvent).not.toBe('ev-tournament-10') }) it('点将赛出:三关全胜计入 rank1 与声望红利', () => { const w = baseWorld('tr-b') w.state.members['x3'].realm = { major: 'foundation', minor: 0 } runTournament(w, ['x3']) const rec = w.state.stats.tourneyHistory[w.state.stats.tourneyHistory.length - 1] expect(rec).toBeTruthy() expect(rec!.year).toBe(w.state.year) expect(rec!.rank).toBeGreaterThanOrEqual(1) expect(rec!.rank).toBeLessThanOrEqual(4) expect(w.state.battles.length).toBeGreaterThanOrEqual(1) }) it('无人可遣时告假缺席不崩溃', () => { const w = baseWorld('tr-c') for (const c of Object.values(w.state.members)) { c.state = 'apprentice' } runTournament(w) expect(w.state.stats.tourneyHistory.length).toBe(0) }) it('称病不出选项:声望-3 且有最低记账', () => { const w = baseWorld('tr-d') w.state.year = 10 const rep0 = w.state.family.reputation applyEventChoice(w, 'ev-tournament-10', 2) // 称病 expect(w.state.family.reputation).toBe(rep0 - 3) }) }) describe('apprentice 宗门寄读', () => { it('招生条件与派遣', () => { const w = baseWorld('ap-a') w.state.members['x5'].bornYear = 1 - 15 // 15 岁 const cands = apprenticeCandidates(w) expect(cands.length).toBeGreaterThan(0) buildApprentice(w) const apprentice = Object.values(w.state.members).find((c) => c.state === 'apprentice') expect(apprentice).toBeTruthy() expect(apprentice!.apprentice?.untilYear).toBeGreaterThan(w.state.year) }) it('寄读者不修炼不婚配', () => { const w = baseWorld('ap-b') const c = w.state.members['x5'] c.bornYear = 1 - 15 buildApprentice(w) const p0 = c.realmProgress w.advanceMonth() expect(c.realmProgress).toBe(p0) // candidates 不含 expect(w.marriageCandidatesOf('x3').map((x) => x.id)).not.toContain(c.id) }) it('到期归家:境界推进与状态复原', () => { const w = baseWorld('ap-c') const c = w.state.members['x3'] c.bornYear = 1 - 30 c.state = 'apprentice' c.apprentice = { sect: '云栖宗', untilYear: 1, quiet: false } finalizeApprentice(w, c.id, 'return') expect(c.state).toBe('idle') expect(c.apprentice).toBeUndefined() // 归来必定取得进步:要么修为涨,要么已突破 const advanced = c.realmProgress >= 60 || c.realm.minor > 1 || c.realm.major !== 'qi' expect(advanced).toBe(true) }) it('续读再延二年', () => { const w = baseWorld('ap-d') const c = w.state.members['x3'] c.state = 'apprentice' c.apprentice = { sect: '太谷书院', untilYear: 5, quiet: false } finalizeApprentice(w, c.id, 'stay') expect(c.apprentice?.untilYear).toBe(Math.max(5, w.state.year + 2)) expect(c.state).toBe('apprentice') }) it('寄读期满自动推事件', () => { const w = baseWorld('ap-e') const c = w.state.members['x3'] c.state = 'apprentice' c.apprentice = { sect: '丹霞洞天', untilYear: w.state.year, quiet: false } w.advanceMonth() const queued = w.state.eventQueue.some((id) => id.startsWith('ev-apprentice-')) expect(queued).toBe(true) // smoke保证 year%4 的招生在年份匹配时也 fire(不影响上述) }) }) describe('seekSutra 求经台', () => { it('藏书阁>=3 才可、两年冷却、收获入藏', () => { const w = baseWorld('sk-a') w.state.family.buildings = { cangshu: 2 } expect(w.seekSutra()).toBe(false) w.state.family.buildings = { cangshu: 3 } w.state.family.stones = 1000 const before = w.state.family.techniques.length expect(w.seekSutra()).toBe(true) expect(w.state.family.techniques.length).toBeGreaterThanOrEqual(before) expect(w.seekSutra()).toBe(false) // 冷却 w.state.year += 2 expect(w.seekSutra()).toBe(true) }) })