test: 测试矩阵扩至 161 项(深度审计 + 5 处缺陷修复)
新增测试矩阵(12 文件 / 161 cases): - rng 12 / data 18 / pcgen+cultivation 20 / economy 12 / combat+missions 24 - marriage+diplomacy 13 / events 12 / world+lifecycle 8(含镜像一致)/ save 4+5 / audit 8 审计带出并修复的真实缺陷: 1. Rng.pick 空数组未报错(防空白数组进而崩溃难查) 2. combatPowerOf 未应用功法悟道品阶战力加成(0.1.2 设计缺口) 3. 重伤者即使不代表伤状态仍按满速修炼(health 进修炼公式) 4. 寡妇/鳏夫无法再婚:候选列表放着人执行层却拒婚(spouseId 死旧契约) 5. isWidowed 参数契约陷阱:传 id 静默 false(升级接受 Character|Id 双签名)
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { World } from '../src/renderer/game/engine/world'
|
||||
import { Rng, seedToRng } from '../src/renderer/game/core/rng'
|
||||
import { newCharacter, rollAttributes, rollRoots, rollPersonality, traitBonuses, calcLifespan } from '../src/renderer/game/engine/pcgen'
|
||||
import { monthlyRate, perAttemptChance } from '../src/renderer/game/engine/systems/cultivation'
|
||||
import { lifespanOf } from '../src/renderer/game/engine/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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user