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,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)
|
||||
}
|
||||
Reference in New Issue
Block a user