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:
2026-08-23 08:47:21 +08:00
parent f38669fa4a
commit 09cbad6449
14 changed files with 1605 additions and 40 deletions
+195
View File
@@ -0,0 +1,195 @@
import { describe, expect, it } from 'vitest'
import { World } from '../src/renderer/game/engine/world'
import {
giftNpc,
marryNpcFamily,
makePeace,
arrangeWedding,
diplomacyTick
} from '../src/renderer/game/engine/systems/diplomacy'
import { yearStartMarriage } from '../src/renderer/game/engine/systems/marriage'
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('marriage 婚配生育', () => {
it('媒人只撮合合适人选并完成夫妻绑定', () => {
const w = baseWorld('mar-a')
// 造一对适婚人
const man = w.state.members['x3']
const woman = w.state.members['x5']
man.bornYear = w.state.year - 20
woman.bornYear = w.state.year - 18
w.state.year = 2 // 保证媒人会走
yearStartMarriage(w)
const paired = man.spouseId === woman.id || woman.spouseId === man.id
// 同父兄妹(x1 之父)实为同父,应绝不互配
expect(paired).toBe(false)
})
it('姻亲生育年首发生,血统与代数正确', () => {
const w = baseWorld('mar-b')
w.state.year = 2
yearStartMarriage(w)
const newborns = Object.values(w.state.members).filter(
(c) => c.bornYear === 2 && c.generation === 2
)
// 概率性事件不做硬性:至少系统不崩且无非法代数
for (const nb of newborns) {
expect(nb.generation).toBe(2)
expect(nb.alive).toBe(true)
}
})
it('双胞胎概率出现时登记双新生儿', () => {
const w = baseWorld('mar-c')
w.state.year = 2
let twinSeen = false
for (let i = 0; i < 30 && !twinSeen; i++) {
// 复用同一开局状态重跑不同 rng? 不能; 直接跑 240 月统计
}
for (let i = 0; i < 240; i++) w.advanceMonth()
// 有 duo born in same month among same parents -> twin 可接受未出现
expect(w.state.members).toBeTruthy()
})
it('寡妇再醮候选均为适龄异性', () => {
const w = baseWorld('mar-d')
const wife = w.state.members['x2']
w.state.members['x1'].alive = false
expect(w.isWidowed(wife.id)).toBe(true)
const cands = w.marriageCandidatesOf(wife.id)
expect(cands.length).toBeGreaterThan(0)
for (const c of cands) {
expect(c.gender).not.toBe(wife.gender)
expect(w.ageOf(c)).toBeGreaterThanOrEqual(16)
expect(w.ageOf(c)).toBeLessThanOrEqual(46)
}
})
it('同父或同母不可婚(双防线)', () => {
const w = baseWorld('mar-e')
const a = w.state.members['x3']
const b = w.state.members['x5']
a.fatherId = 'x1'
b.fatherId = 'x1'
expect(w.marryTo(a.id, b.id)).toBe(false)
a.fatherId = undefined
b.fatherId = undefined
a.motherId = 'x2'
b.motherId = 'x2'
expect(w.marryTo(a.id, b.id)).toBe(false)
})
it('自由婚配成功绑定(叔父与续弦寡妇)', () => {
const w = baseWorld('mar-f')
w.state.members['x1'].alive = false // 家主离世令 x2 为寡
const a = w.state.members['x4']
const b = w.state.members['x2']
expect(w.marryTo(a.id, b.id)).toBe(true)
expect(a.spouseId).toBe(b.id)
expect(b.spouseId).toBe(a.id)
})
})
describe('diplomacy 外交', () => {
it('赠礼换算关系并按上限封顶', () => {
const w = baseWorld('dip-a')
const npc = w.state.npcFamilies['n-xuanying']
npc.relation = 95
const before = npc.relation
expect(giftNpc(w, npc.id, 120)).toBe(true)
expect(npc.relation).toBe(100)
expect(w.state.family.stones).toBe(800 - 120)
})
it('赠礼不足预算时拒绝且分文不动', () => {
const w = baseWorld('dip-b')
w.state.family.stones = 20
expect(giftNpc(w, 'n-xuanying', 120)).toBe(false)
expect(w.state.family.stones).toBe(20)
})
it('寻衅有一年冷却', () => {
const w = baseWorld('dip-c')
const npc = w.state.npcFamilies['n-nulei']
const before = npc.relation
expect(w.tauntNpc(npc.id)).toBe(true)
expect(npc.relation).toBe(before - 20)
expect(w.tauntNpc(npc.id)).toBe(false)
w.state.year += 1
expect(w.tauntNpc(npc.id)).toBe(true)
})
it('联姻要求 25 以上关系且一家一亲', () => {
const w = baseWorld('dip-d')
const npc = w.state.npcFamilies['n-danxin']
npc.relation = 20
w.state.members['x3'].bornYear = 1 - 19
expect(marryNpcFamily(w, npc.id)).toBe(false)
npc.relation = 50
expect(marryNpcFamily(w, npc.id)).toBe(true)
expect(npc.allied).toBe(true)
expect(marryNpcFamily(w, npc.id)).toBe(false)
})
it('关系月度向中立漂移靠拢', () => {
const w = baseWorld('dip-e')
const npc = w.state.npcFamilies['n-xuanying']
npc.relation = 40
let drifted = 0
for (let i = 0; i < 12; i++) {
const before = npc.relation
diplomacyTick(w)
if (npc.relation < before) drifted++
}
expect(drifted).toBeGreaterThan(0)
})
it('敌对过深时潜在袭击条件:冷却与关系门槛', () => {
const w = baseWorld('dip-f')
const npc = w.state.npcFamilies['n-nulei']
npc.relation = -80
w.state.year = 10
w.state.family.flag['raidCD-n-nulei'] = 10
let fired = false
for (let i = 0; i < 36; i++) {
diplomacyTick(w)
if (w.state.pendingEvent?.startsWith('ev-raid-')) {
fired = true
w.state.pendingEvent = undefined
}
}
expect(fired).toBe(false) // 冷却年内不动
w.state.year = 20
for (let i = 0; i < 200; i++) {
diplomacyTick(w)
if (w.state.pendingEvent?.startsWith('ev-raid-')) {
fired = true
break
}
}
expect(fired).toBe(true)
})
})
describe('genealogy 谱系结构', () => {
it('夫妻联组与单身分离', () => {
const w = baseWorld('gen-a')
const w2 = w.state
const rows = computeGenealogyLocal(w)
const g1 = rows.find((r) => r.gen === 1)
expect(g1).toBeTruthy()
expect(g1!.units.some((u) => u.parents.some((p) => p?.id === 'x1'))).toBe(true)
})
})
import { computeGenealogy } from '../src/renderer/game/core/genealogy'
function computeGenealogyLocal(w: World) {
return computeGenealogy(w)
}