Files
ChronicleOfTheImmortalClan/src/renderer/game/engine/runtime/Systems/marriage.ts
T
thzxx a99420da09 v0.1.29: 续命——第四轮全量审计歼灭(2014 全绿)
【P0-A 续代断链(3seed×183年实测:170 年零出生老人院)——本轮最重】
- 生育窗随境界延长(化神 300+ 可育);天年=min(span,200)(850 寿元永不达的死亡回归)
- 族外婚注入(媒人枯竭引外姓——0.1.1 至今缺失的最后一环)
- 全局年胎 cap(≤18 人年 3 胎/18-24 1 胎/>24 停)→ 稳态 ~25 人、gens 3、2200 月 420ms
- 确定性回归:inlaw 判定长度化+单一 chance(修复过程中的真 bug——det 二分锁定)

【P1 七件】legacypass 年锚(同一年 12 连发 224 次实测)/敌强度相对化(0.35+rel×0.55,
cap 2.6——5000+ 战力零风险碾压的定锚)/eventRoll 插件权重防垄断(>核心 50% 压缩)/
allEvents 按 enabled 过滤(禁用 MOD 事件双闸全面兑现)/bundle 全量导入(modParseAll 导出 2 回 2)/
chronicle 表治理(write-only→no-op)/Stash 并发(open in-flight 去重+save try)
+ PluginManager catch 全回滚 + about 版本统一

【P2 群】decline 150 激活生灭/化神月俸 20 石泄灵石死水/灾因 brief+descOf 接线/
worldGenMods 落空/EventModal 文案对齐/LegacyPanel 恒等清理

【测试】70 套件 2014 全绿(audit-0.1.29 六例:人口曲线/传薪年锚/强度公式/bundle 全量/异常回滚/灾因 brief);
【金钟罩】0.1.29 基线重算(续代/战斗/事件数值受控变更);build 通过
2026-08-23 20:14:42 +08:00

161 lines
6.6 KiB
TypeScript

import type { World } from '../World'
import { Character } from '../../../types/domain'
import { produceOffspring } from './cultivation'
import { yearGrowth } from './diplomacy'
import { MALE_GIVEN, FEMALE_GIVEN } from '../../kernel/names'
import { aspirationById } from '../../../data/aspirations'
import { lifespanOf } from './lifecycle'
export function yearStartMarriage(w: World): void {
yearGrowth(w)
const s = w.state
const fam = s.family
const zongci = fam.buildings['zongci'] ?? 0
const birthBase = 0.6 + zongci * 0.04 + (fam.difficulty === 'easy' ? 0.06 : fam.difficulty === 'hard' ? -0.06 : 0)
const couples = buildCouples(w)
// 族内夫妇
let yearBirths = 0
const pop = Object.values(s.members).filter((m) => m.alive).length
for (const couple of couples.internal) {
const [a, b] = couple
const father = a.gender === 'male' ? a : b
const mother = a.gender === 'male' ? b : a
const fatherAge = w.ageOf(father)
const motherAge = w.ageOf(mother)
// P0-A 续代:生育窗随境界延长(高境界者容颜常驻、生育期延)
const fatherLife = lifespanOf(w, father)
const motherLife = lifespanOf(w, mother)
const fatherMax = Math.min(520, 52 + (fatherLife - 75) * 0.35)
const motherMax = Math.min(520, 46 + (motherLife - 75) * 0.35)
if (fatherAge < 18 || fatherAge > fatherMax || motherAge < 16 || motherAge > motherMax) continue
const offspringBonus = Object.values(s.members)
.filter((m) => m.alive && aspirationById(m.aspiration)?.effect.type === 'offspring')
.length * 0.05
// P0-A 人口卫生:生育率稳定在 0.2~0.35/对/年;家族进入大龄期再衰减(防指数爆炸)
// P0-A 人口卫生(终版):全局年胎 cap 按人口档——小族多生、大族停育(稳态 ~20 人防爆炸)
if (yearBirths >= 3 || pop > 24) continue
if (pop > 18 && yearBirths >= 1) continue
const p = Math.min(0.6, birthBase * 0.35 * (0.75 + mother.physique * 0.05)) + offspringBonus
if (!w.rng.chance(p)) continue
yearBirths++
const gen = Math.max(father.generation, mother.generation) + 1
const first = produceOffspring(w, { father, mother, generation: gen, bornYear: s.year, surname: fam.surname })
w.addMember(first, father, mother)
let note = `${fam.surname}氏新增一员,名唤${first.name},生年 ${s.year}。`
if (w.rng.chance(0.06)) {
const twin = produceOffspring(w, { father, mother, generation: gen, bornYear: s.year, surname: fam.surname })
w.addMember(twin, father, mother)
w.state.yearStats.births += 1
note += ` 双生之喜!双子名唤${twin.name}。`
}
w.chronicle('birth', note, first.id, true)
w.state.yearStats.births += 1
w.log('good', `${fam.surname}家诞下新丁:${first.name}。`)
}
// 联姻外孙来投(男方娶亲:妻室携子来归;或女儿携子回门)——与族内育共用人口 cap
for (const member of Object.values(s.members)) {
if (!member.alive || !member.spouseHouse) continue
const age = w.ageOf(member)
if (age < 18 || age > 44) continue
if (yearBirths >= 2 || pop > 24) continue
if (!w.rng.chance(0.15)) continue
const gen = member.generation + 1
const house = member.spouseHouse
const child = produceOffspring(w, { father: null, mother: null, generation: gen, bornYear: s.year, surname: fam.surname })
child.spouseHouse = house
w.addMember(child)
w.chronicle('birth', `${member.name}${house}携幼子归庄,名唤${child.name}。`, child.id, true)
w.state.yearStats.births += 1
w.log('info', `${member.name} 领着小辈回门投亲。`)
}
// 媒人撮合族内婚(含续弦与再醮)
const isWidowed = (c: Character): boolean => {
if (!c.spouseId) return false
const sp = w.state.members[c.spouseId]
return !!sp && !sp.alive
}
const men = w
.aliveMembers()
.filter((c) => c.gender === 'male' && c.state !== 'expedition' && (isWidowed(c) || !c.spouseId))
.filter((c) => {
const age = w.ageOf(c)
return age >= 18 && age <= 48
})
const women = w
.aliveMembers()
.filter((c) => c.gender === 'female' && c.state !== 'expedition' && (isWidowed(c) || !c.spouseId))
.filter((c) => {
const age = w.ageOf(c)
return age >= 16 && age <= 42
})
// P0-A 族外婚注入:仅当真实缺口(适龄异性为 0)+ 人口上限(确定性判定=长度)
const aliveM = w.aliveMembers().length
const inlawNeeded =
aliveM < 24 &&
men.length > 0 && women.length === 0 && w.rng.chance(0.8)
const inlawFemaleNeeded =
aliveM < 24 &&
women.length > 0 && men.length === 0 && w.rng.chance(0.8)
if (inlawNeeded || inlawFemaleNeeded) {
const gender = inlawNeeded ? 'female' : 'male'
const inlaw = produceOffspring(w, {
father: null,
mother: null,
generation: 1,
bornYear: s.year - (18 + w.rng.int(0, 10)),
surname: '远' as never
})
inlaw.spouseHouse = '外姓'
w.addMember(inlaw)
if (gender === 'male') men.push(inlaw)
else women.push(inlaw)
w.log('info', `媒人引介一位${gender === 'male' ? '外姓男' : '外姓女'}士入族相亲。`)
}
for (const m of men) {
if (w.rng.chance(0.55) && women.length > 0) {
const candidate = women.filter(
(x) =>
!(x.fatherId && x.fatherId === m.fatherId) &&
!(x.motherId && x.motherId === m.motherId) &&
x !== m &&
!x.children.includes(m.id) &&
!m.children.includes(x.id)
)
if (candidate.length === 0) continue
const bride = w.rng.pick(candidate)
m.spouseId = bride.id
bride.spouseId = m.id
w.chronicle('marriage', `${m.name}${bride.name}缔结连理。`, m.id, true)
w.log('info', `${m.name}${bride.name} 成婚。`)
const idx = women.indexOf(bride)
if (idx >= 0) women.splice(idx, 1)
}
}
fam.generation = Math.max(
fam.generation,
...Object.values(s.members).filter((c) => c.alive).map((c) => c.generation)
)
}
function buildCouples(w: World): { internal: [Character, Character][]; cross: Character[] } {
const internal: [Character, Character][] = []
const cross: Character[] = []
for (const member of Object.values(w.state.members)) {
if (!member.alive || !member.spouseId) continue
const spouse = w.state.members[member.spouseId]
if (!spouse?.alive) continue
const key = [member.id, spouse.id].sort().join('|')
if (internal.some(([a, b]) => [a.id, b.id].sort().join('|') === key)) continue
internal.push([member, spouse])
}
for (const member of Object.values(w.state.members)) {
if (member.alive && member.spouseHouse) cross.push(member)
}
return { internal, cross }
}