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) }