import { describe, expect, it } from 'vitest' import { World } from '../src/renderer/game/engine/runtime/World' import { Rng, seedToRng } from '../src/renderer/game/engine/kernel/rng' import { newCharacter, rollAttributes, rollRoots, rollPersonality, traitBonuses, calcLifespan } from '../src/renderer/game/engine/runtime/pcgen' import { monthlyRate, perAttemptChance } from '../src/renderer/game/engine/runtime/Systems/cultivation' import { lifespanOf } from '../src/renderer/game/engine/runtime/Systems/lifecycle' function rng(): Rng { return new Rng(seedToRng('pcgen')) } describe('pcgen 人物生成', () => { it('rollAttributes 限制在 1~10', () => { const r = rng() for (let i = 0; i < 300; i++) { const v = rollAttributes(r, 5, 3) expect(v).toBeGreaterThanOrEqual(1) expect(v).toBeLessThanOrEqual(10) } }) it('rollRoots 品阶在 0~5,副属性与主属性不重复', () => { const r = rng() for (let i = 0; i < 50; i++) { const roots = rollRoots(r) expect(roots.grade).toBeGreaterThanOrEqual(0) expect(roots.grade).toBeLessThanOrEqual(5) expect(roots.secondary.length).toBeLessThanOrEqual(2) expect(roots.secondary).not.toContain(roots.primary) expect(new Set(roots.secondary).size).toBe(roots.secondary.length) } }) it('父母品阶子代平均贴近父母', () => { const r = rng() const dad = newCharacter(rng(), { name: '父', gender: 'male', generation: 1, bornYear: -20, age: 20, realm: { major: 'qi', minor: 1 } }) dad.roots = { grade: 4, primary: '火', secondary: [] } const mom = newCharacter(rng(), { name: '母', gender: 'female', generation: 1, bornYear: -20, age: 20, realm: { major: 'qi', minor: 1 } }) mom.roots = { grade: 4, primary: '火', secondary: [] } let near = 0 for (let i = 0; i < 40; i++) { const child = rollRoots(r, { m: dad, f: mom }) if (child.grade >= 3) near++ } // 双天品父母子代大概率不劣于玄品 expect(near).toBeGreaterThan(25) }) it('rollPersonality 返回 1~2 个合法特质', () => { const r = rng() for (let i = 0; i < 50; i++) { const t = rollPersonality(r) expect(t.length).toBeGreaterThanOrEqual(1) expect(t.length).toBeLessThanOrEqual(2) expect(new Set(t).size).toBe(t.length) } }) it('traitBonuses 聚合回值在合理区间', () => { const chara = newCharacter(rng(), { name: '某人', gender: 'male', generation: 1, bornYear: -20, age: 20 }) chara.traits = ['zisheng', 'jingkan'] // 自律+精研 const b = traitBonuses(chara) expect(b.exp).toBeGreaterThan(1) expect(b.breakBonus + b.charmBonus).toBeGreaterThanOrEqual(0) chara.traits = ['xinheng'] // 性狠 expect(traitBonuses(chara).windBonus).toBeGreaterThan(0) }) it('calcLifespan 随根骨略增长且为正', () => { const low = calcLifespan('qi', 1) const high = calcLifespan('qi', 9) expect(high).toBeGreaterThan(low) expect(low).toBeGreaterThan(0) }) }) describe('monthlyRate 修炼速率', () => { function baseWorld(seed: string): World { return World.create({ seed, surname: '尹', familyName: '尹家', motto: 'm', difficulty: 'normal' }) } it('感知越高修得越快', () => { const w = baseWorld('cult-a') const c = w.state.members['x5'] c.realm = { major: 'qi', minor: 1 } c.realmProgress = 0 c.perception = 3 const low = monthlyRate(w, c) c.perception = 9 const high = monthlyRate(w, c) expect(high).toBeGreaterThan(low * 1.6) }) it('闭关显著快于闲居', () => { const w = baseWorld('cult-b') const c = w.state.members['x5'] c.realm = { major: 'qi', minor: 1 } c.state = 'idle' const idle = monthlyRate(w, c) c.state = 'meditation' const med = monthlyRate(w, c) expect(med).toBeGreaterThan(idle * 1.25) }) it('负伤大幅减速,重伤近乎停滞', () => { const w = baseWorld('cult-c') const c = w.state.members['x5'] c.realm = { major: 'qi', minor: 1 } c.health = 80 const mild = monthlyRate(w, c) c.health = 15 const grave = monthlyRate(w, c) expect(grave).toBeLessThan(mild * 0.6) }) it('童子启蒙与暮年衰退', () => { const w = baseWorld('cult-d') const c = w.state.members['x5'] c.realm = { major: 'qi', minor: 1 } c.bornYear = w.state.year // 0 岁 const kid = monthlyRate(w, c) c.bornYear = w.state.year - 70 const elder = monthlyRate(w, c) const adult = (() => { const c2 = w.state.members['x4'] c2.bornYear = w.state.year - 30 return monthlyRate(w, c2) })() expect(kid).toBeLessThan(adult) expect(elder).toBeLessThan(adult) }) it('聚灵阵与洞府对闭关加成', () => { const w = baseWorld('cult-e') w.state.family.buildings['juling'] = 2 w.state.family.buildings['dongfu'] = 2 const c = w.state.members['x5'] c.realm = { major: 'qi', minor: 1 } c.state = 'meditation' const withB = monthlyRate(w, c) w.state.family.buildings = { lingtian: 1, zongci: 1 } const without = monthlyRate(w, c) expect(withB).toBeGreaterThan(without) }) it('无功法散修明显慢于有功者', () => { const w = baseWorld('cult-f') const c1 = w.state.members['x5'] c1.realm = { major: 'qi', minor: 1 } c1.techniqueId = 't-qinglian' const withT = monthlyRate(w, c1) c1.techniqueId = undefined const without = monthlyRate(w, c1) expect(withT).toBeGreaterThan(without) }) it('大境界修炼效率递减(元婴比炼气慢)', () => { const w = baseWorld('cult-g') const c1 = w.state.members['x5'] c1.realm = { major: 'qi', minor: 1 } const qi = monthlyRate(w, c1) c1.realm = { major: 'nascent', minor: 0 } const nas = monthlyRate(w, c1) expect(nas).toBeLessThan(qi) }) it('perAttemptChance 受心性与性格影响(勇气)', () => { const w = baseWorld('cult-h') const c = w.state.members['x5'] c.realm = { major: 'qi', minor: 8 } c.realmProgress = 100 c.mind = 3 c.traits = [] const low = perAttemptChance(w, c) c.mind = 9 c.traits = ['tiangan'] const high = perAttemptChance(w, c) expect(high).toBeGreaterThan(low) expect(low).toBeGreaterThan(0.4) }) it('瓶颈自动尝试存在 6 个月冷却', () => { const w = baseWorld('cult-i') const c = w.state.members['x5'] c.realm = { major: 'qi', minor: 1 } c.realmProgress = 100 c.lastBreakthroughAttempt = w.state.year * 12 + w.state.month - 3 const before = c.realmProgress // 应保持 100(冷却中不自动尝试) w.advanceMonth() expect(c.realmProgress).toBeGreaterThanOrEqual(100) // 仍 ≥100(要么未尝试要么成功从0算) // 未尝试则保持 100;尝试要么成功(realm 变化或者 progress=0) const plausible = c.realmProgress >= 100 || (c.realmProgress < 100 && c.realm.major === 'qi' && c.realm.minor === 1) || c.realm.major !== 'qi' expect(plausible).toBe(true) void before }) }) describe('lifecycle 寿命', () => { it('50 岁修士不会自然死亡(远早于寿元)', () => { const w = World.create({ seed: 'life-a', surname: '秦', familyName: '秦家', motto: 'm', difficulty: 'normal' }) const c = w.state.members['x1'] c.bornYear = w.state.year - 50 for (let i = 0; i < 24; i++) w.advanceMonth() expect(c.alive).toBe(true) }) it('寿元临界后逐渐死亡(模拟 60 周年内全部辞世)', () => { const w = World.create({ seed: 'life-b', surname: '雷', familyName: '雷家', motto: 'm', difficulty: 'easy' }) const c = w.state.members['x5'] c.bornYear = w.state.year - 999 // 深过期 let died = false for (let i = 0; i < 120 && !died; i++) { w.advanceMonth() died = !c.alive } expect(died).toBe(true) expect(c.deathCause === '幼夭' || c.deathCause === '寿元将尽' || c.deathCause === '伤势不治').toBe(true) }) it('重伤平复机制:伤者每月回复→健康', () => { const w = World.create({ seed: 'life-c', surname: '盼', familyName: '盼家', motto: 'm', difficulty: 'normal' }) const c = w.state.members['x3'] c.health = 10 c.state = 'wounded' const h0 = c.health w.advanceMonth() expect(c.health).toBeGreaterThan(h0) // 不断推进最终恢复闲居 for (let i = 0; i < 12; i++) w.advanceMonth() expect(c.health).toBeGreaterThanOrEqual(95) if (c.alive && c.health >= 95) expect(c.state).toBe('idle') }) it('重伤过低小幅增加早逝风险(抽样验证无崩溃且部分死亡)', () => { const w = World.create({ seed: 'life-d', surname: '荣', familyName: '荣家', motto: 'm', difficulty: 'normal' }) const c = w.state.members['x4'] c.bornYear = w.state.year - 999 // 未到寿限前不会因这点死亡; 直接推到长期 c.health = 1 c.physique = 1 let deaths = 0 for (let i = 0; i < 150; i++) w.advanceMonth() deaths = !c.alive ? 1 : 0 if (deaths) expect(c.deathCause).toBeTruthy() }) }) describe('lifespanOf 计算', () => { it('随大境界与根骨增长', () => { const w = World.create({ seed: 'ls', surname: '魏', familyName: '魏家', motto: 'm', difficulty: 'normal' }) const c = w.state.members['x3'] c.realm = { major: 'qi', minor: 1 } c.physique = 4 const qi = lifespanOf(w, c) c.realm = { major: 'core', minor: 0 } const core = lifespanOf(w, c) expect(core).toBeGreaterThan(qi) c.physique = 8 expect(lifespanOf(w, c)).toBeGreaterThan(qi) }) })