import { describe, expect, it } from 'vitest' import { World } from '../src/renderer/game/engine/world' import { monthlyRate, resolveBreakthrough } from '../src/renderer/game/engine/systems/cultivation' import { combatPowerOf } from '../src/renderer/game/engine/systems/combat' import { isFit } from '../src/renderer/game/data/aspirations' import { needsTribulation, resolveTribulation, tribulationEventId } from '../src/renderer/game/engine/systems/tribulation' import { buildBiography } from '../src/renderer/game/core/biography' 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('aspirations 志向', () => { it('成年礼自动定志(16-19 岁补充)', () => { const w = baseWorld('asp-a') const c = w.state.members['x3'] // 16 岁 w.advanceMonth() expect(c.aspiration).toBeTruthy() }) it('求道志向修炼加快,云游稍缓', () => { const w = baseWorld('asp-b') const c = w.state.members['x5'] c.realm = { major: 'qi', minor: 1 } c.aspiration = 'dao' const dao = monthlyRate(w, c) c.aspiration = 'yun' const yun = monthlyRate(w, c) expect(dao).toBeGreaterThan(yun) c.aspiration = undefined const none = monthlyRate(w, c) expect(dao).toBeGreaterThan(none) }) it('兵略志向提升战力,商略不影响战力', () => { const w = baseWorld('asp-c') const c = w.state.members['x4'] c.aspiration = 'bing' const pb = combatPowerOf(w, c) c.aspiration = 'shang' const ps = combatPowerOf(w, c) expect(pb).toBeGreaterThan(ps) }) it('耕读志向提升灵田产出', () => { const w = baseWorld('asp-d') w.state.family.buildings = { lingtian: 1 } w.state.members['x3'].aspiration = 'geng' const c0 = w.state.family.inventory['lingcao'] ?? 0 w.advanceMonth() const gained = (w.state.family.inventory['lingcao'] ?? 0) - c0 expect(gained).toBe(11) }) it('承宗志向提升家族添丁率', () => { const w = baseWorld('asp-e') let births = 0 for (const c of Object.values(w.state.members)) { c.aspiration = 'cheng' if (c.gender === 'male' && c.id !== 'x3') c.state = 'idle' } for (let i = 0; i < 120; i++) w.advanceMonth() for (const c of Object.values(w.state.members)) { if (c.bornYear > 1) births++ } expect(births).toBeGreaterThanOrEqual(1) }) }) describe('灵根契合', () => { it('本命判定按主属性', () => { expect(isFit('火', '火')).toBe(true) expect(isFit('水', '火')).toBe(false) }) it('契合者修炼更快', () => { const w = baseWorld('fit-a') const c = w.state.members['x5'] c.realm = { major: 'qi', minor: 1 } c.techniqueId = 't-liehuo' // 火 c.roots = { grade: 3, primary: '火', secondary: [] } const fit = monthlyRate(w, c) c.roots = { grade: 3, primary: '水', secondary: [] } const nofit = monthlyRate(w, c) expect(fit).toBeGreaterThan(nofit) }) it('契合者战力更高', () => { const w = baseWorld('fit-b') const c = w.state.members['x4'] c.techniqueId = 't-liehuo' c.roots = { grade: 3, primary: '火', secondary: [] } const fit = combatPowerOf(w, c) c.roots = { grade: 3, primary: '水', secondary: [] } const nofit = combatPowerOf(w, c) expect(fit).toBeGreaterThan(nofit) }) }) describe('渡劫', () => { it('仅大境界晋升触发', () => { const w = baseWorld('trb-a') const c = w.state.members['x3'] c.realm = { major: 'qi', minor: 1 } expect(needsTribulation(c)).toBe(false) c.realm = { major: 'qi', minor: 8 } expect(needsTribulation(c)).toBe(true) c.realm = { major: 'foundation', minor: 2 } expect(needsTribulation(c)).toBe(true) c.realm = { major: 'spirit', minor: 2 } expect(needsTribulation(c)).toBe(false) }) it('触发后事件 id 唯一且可解析', () => { const w = baseWorld('trb-b') const c = w.state.members['x3'] c.realm = { major: 'foundation', minor: 2 } c.realmProgress = 100 w.advanceMonth() // 冷却 6 月保护下每月 chance 尝试——长跑 40 月要么事件出现要么仍在等待 let found = false for (let i = 0; i < 40 && !found; i++) { w.advanceMonth() if (w.state.pendingEvent?.startsWith('ev-trib-')) found = true } expect(found).toBe(true) }) it('硬渡成功晋升大境界', () => { const w = baseWorld('trb-c') let success = false for (let attempt = 0; attempt < 30 && !success; attempt++) { const c = w.state.members['x3'] c.realm = { major: 'qi', minor: 9 } c.realmProgress = 100 c.mind = 9 c.health = 100 const r = resolveTribulation(w, c, 'rash') if (r === 'success') { expect(c.realm.major).toBe('foundation') success = true } else { // 失败后被折断,复置后重试(保持独立样本) c.realmProgress = 100 c.health = 100 } } expect(success).toBe(true) }) it('压制一年延迟后恢复尝试通道', () => { const w = baseWorld('trb-d') const c = w.state.members['x3'] c.realm = { major: 'qi', minor: 9 } c.realmProgress = 100 resolveTribulation(w, c, 'delay') expect(c.tribDelayYear).toBe(w.state.year + 1) // 未到年份时 cultivationTick 锁定 => 不会立动 w.advanceMonth() expect(c.realmProgress).toBe(100) }) it('护法失败时护法可能受伤但不会陨落', () => { const w = baseWorld('trb-e') const c = w.state.members['x3'] c.realm = { major: 'qi', minor: 9 } c.realmProgress = 100 c.mind = 1 // 低心跳高失败 c.health = 100 const guardian = w.state.members['x4'] guardian.health = 100 const r = resolveTribulation(w, c, 'guard') if (r === 'fail') { expect(guardian.alive).toBe(true) } expect(w.state.family.stones).toBeLessThanOrEqual(800) }) }) describe('biography 列传', () => { it('活在生人:生平/修行脉络/婚育齐全', () => { const w = baseWorld('bio-a') const lines = buildBiography(w.state, 'x1') const text = lines.map((l) => l.text).join('') expect(text).toContain('第1代') expect(text).toContain('生于-34年') expect(lines.length).toBeGreaterThan(0) }) it('逝者带墓志铭且包含死因', () => { const w = baseWorld('bio-b') const c = w.state.members['x5'] c.alive = false c.deathYear = 40 c.deathCause = '渡劫陨落' const lines = buildBiography(w.state, x5id(w)) const last = lines[lines.length - 1] expect(last.label).toBe('墓志') expect(last.text).toContain('雷霆') // 渡劫陨落专属纹样 }) it('突破脉络随史书记录增长', () => { const w = baseWorld('bio-c') const c = w.state.members['x4'] c.realm = { major: 'qi', minor: 8 } c.realmProgress = 100 c.mind = 9 resolveBreakthrough(w, c, 0.9) const lines = buildBiography(w.state, c.id) expect(lines.some((l) => l.label === '修行')).toBe(true) }) }) function x5id(w: World): string { const c = Object.values(w.state.members).find((m) => m.id === 'x5')! return c.id }