v0.1.10: 长线决断(数值重建 + 长跑鉴证)
评估发现(3000月×6种子实证): - 旧数值在正常难度 6/6 种子 90-125 年绝嗣(修炼慢+寿元短+渡劫卡死三重叠加) - 关键缺陷:渡劫事件被 eventRoll 误压制(qi8→筑基永不可达),境界峰恒为炼气 重建(data/ 数值 + engine 修复): - 修炼提速:基率 0.5+感知0.1→1.0+感知0.18;pacing qi 1→2.2(各境界等比提) - 修为曲线:qi expGrowth 1.35→1.10(9层累计工期<28年) - 寿元放宽:凡人75 / 炼气150 / 筑基220 / 金丹340 / 元婴520 / 化神850 - 失败跌损收窄 40-65%→26-44%;衰减点 55→65 岁;突破概率全线+0.1 - 渡劫修复:悬置 12 月自动压制(挂机不软锁)、玩家处理即零计数、 大比 FIFO 不被拍卖/传薪挤掉(queue 兜底) - 新增:仙门拍卖(十月·灵石漏斗)/ 名宿传薪(八年·传承)/ 圣者护族(幼儿修行提速) 鉴证: - 金钟罩升 3 档(47/100/180 年 × 3 seed = 9 指纹固化) - longevity 套件(200 年不败/渡劫晋升/拍卖传薪) - rebuild 套件(速率/工期/寿元/跌损/渡劫率) - 测试 881→905;typecheck/build 通过
This commit is contained in:
@@ -2,11 +2,11 @@ import { RealmMajor } from '../types/domain'
|
||||
|
||||
export const MAJOR_RATE: Record<RealmMajor, number> = {
|
||||
mortal: 1,
|
||||
qi: 1,
|
||||
foundation: 0.62,
|
||||
core: 0.38,
|
||||
nascent: 0.23,
|
||||
spirit: 0.15
|
||||
qi: 2.2,
|
||||
foundation: 1.4,
|
||||
core: 0.95,
|
||||
nascent: 0.68,
|
||||
spirit: 0.48
|
||||
}
|
||||
|
||||
export function masteryRateOfMajor(major: RealmMajor): number {
|
||||
|
||||
@@ -11,12 +11,12 @@ export interface MajorDef {
|
||||
}
|
||||
|
||||
export const MAJORS: Record<RealmMajor, MajorDef> = {
|
||||
mortal: { name: '凡人', short: '凡', minorLayers: 0, lifespan: 62, expBase: 0, expGrowth: 1 },
|
||||
qi: { name: '炼气', short: '炼气', minorLayers: 9, lifespan: 110, expBase: 60, expGrowth: 1.35 },
|
||||
foundation: { name: '筑基', short: '筑基', minorLayers: 3, lifespan: 165, expBase: 1400, expGrowth: 1.6 },
|
||||
core: { name: '金丹', short: '金丹', minorLayers: 3, lifespan: 260, expBase: 6200, expGrowth: 1.7 },
|
||||
nascent: { name: '元婴', short: '元婴', minorLayers: 3, lifespan: 400, expBase: 22000, expGrowth: 1.75 },
|
||||
spirit: { name: '化神', short: '化神', minorLayers: 3, lifespan: 600, expBase: 60000, expGrowth: 1.8 }
|
||||
mortal: { name: '凡人', short: '凡', minorLayers: 0, lifespan: 75, expBase: 0, expGrowth: 1 },
|
||||
qi: { name: '炼气', short: '炼气', minorLayers: 9, lifespan: 150, expBase: 60, expGrowth: 1.10 },
|
||||
foundation: { name: '筑基', short: '筑基', minorLayers: 3, lifespan: 220, expBase: 1400, expGrowth: 1.32 },
|
||||
core: { name: '金丹', short: '金丹', minorLayers: 3, lifespan: 340, expBase: 6200, expGrowth: 1.36 },
|
||||
nascent: { name: '元婴', short: '元婴', minorLayers: 3, lifespan: 520, expBase: 22000, expGrowth: 1.4 },
|
||||
spirit: { name: '化神', short: '化神', minorLayers: 3, lifespan: 850, expBase: 60000, expGrowth: 1.44 }
|
||||
}
|
||||
|
||||
export const MAJOR_ORDER: RealmMajor[] = ['mortal', 'qi', 'foundation', 'core', 'nascent', 'spirit']
|
||||
@@ -91,15 +91,15 @@ export function breakthroughBaseChance(realm: Realm): number {
|
||||
case 'mortal':
|
||||
return 0.9
|
||||
case 'qi':
|
||||
return 0.45
|
||||
return 0.55
|
||||
case 'foundation':
|
||||
return 0.32
|
||||
return 0.45
|
||||
case 'core':
|
||||
return 0.22
|
||||
return 0.32
|
||||
case 'nascent':
|
||||
return 0.14
|
||||
return 0.22
|
||||
case 'spirit':
|
||||
return 0.08
|
||||
return 0.14
|
||||
default:
|
||||
return 0.4
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { needsTribulation, tribulationEventId } from './tribulation'
|
||||
export function monthlyRate(w: World, c: Character): number {
|
||||
const st = w.state
|
||||
let rate = 1
|
||||
rate *= 0.5 + c.perception * 0.1
|
||||
rate *= 1.0 + c.perception * 0.18
|
||||
rate *= ROOT_GRADES[c.roots.grade]?.expBonus ?? 0.5
|
||||
const tech = techniqueById(c.techniqueId)
|
||||
if (tech && c.realm.major !== 'mortal') {
|
||||
@@ -51,12 +51,14 @@ export function monthlyRate(w: World, c: Character): number {
|
||||
}
|
||||
rate *= traitBonuses(c).exp
|
||||
if (w.ageOf(c) < 8) rate *= 0.4
|
||||
if (w.ageOf(c) > 55) rate *= 0.7
|
||||
if (w.ageOf(c) > 65) rate *= 0.72
|
||||
rate *= masteryRateOfMajor(c.realm.major)
|
||||
return rate
|
||||
}
|
||||
|
||||
export function cultivationTick(w: World): void {
|
||||
// 圣者护族:家族有 60+ 且超过筑基者时,幼儿修行门槛更低
|
||||
const hasPatriarch = w.aliveMembers().some((c) => w.ageOf(c) >= 60 && c.realm.major !== 'mortal' && c.realm.major !== 'qi')
|
||||
// 成年礼定志(16-18 岁首次)
|
||||
for (const c of Object.values(w.state.members)) {
|
||||
if (c.alive && !c.aspiration && w.ageOf(c) >= 16 && w.ageOf(c) <= 19) {
|
||||
@@ -91,14 +93,18 @@ export function cultivationTick(w: World): void {
|
||||
const months = (w.state.year * 12 + w.state.month) - (c.lastBreakthroughAttempt ?? -999)
|
||||
if (months >= 6 && w.rng.chance(perAttemptChance(w, c))) {
|
||||
if (needsTribulation(c) && w.sysEnabled('tribulation')) {
|
||||
// 大境界晋升改走渡劫事件(玩家三选);压制者待来年
|
||||
if (!c.tribDelayYear || w.state.year >= c.tribDelayYear) {
|
||||
c.tribDelayYear = undefined
|
||||
w.pendingEvent(tribulationEventId(c))
|
||||
w.state.pendingEvent = tribulationEventId(c)
|
||||
} else {
|
||||
// 已在等待渡劫(pending 未清)或压制期内,不再重设
|
||||
if (w.state.pendingEvent === tribulationEventId(c)) {
|
||||
c.realmProgress = 100
|
||||
continue
|
||||
}
|
||||
if (c.tribDelayYear && w.state.year < c.tribDelayYear) {
|
||||
c.realmProgress = 100
|
||||
continue
|
||||
}
|
||||
c.tribDelayYear = undefined
|
||||
w.pendingEvent(tribulationEventId(c))
|
||||
w.state.pendingEvent = tribulationEventId(c)
|
||||
} else {
|
||||
resolveBreakthrough(w, c, 0)
|
||||
}
|
||||
@@ -186,7 +192,7 @@ export function resolveBreakthrough(w: World, c: Character, boost: number): void
|
||||
return
|
||||
}
|
||||
if (c.health < 20) c.state = 'wounded'
|
||||
const loss = 40 + w.rng.int(0, 25)
|
||||
const loss = 26 + w.rng.int(0, 18)
|
||||
c.realmProgress = Math.max(0, Math.min(95, 100 - loss - boost * 60))
|
||||
w.log('bad', log)
|
||||
}
|
||||
|
||||
@@ -143,6 +143,33 @@ export function dynamicEventFor(id: string, w?: World): EventDef | undefined {
|
||||
]
|
||||
}
|
||||
}
|
||||
if (id === 'ev-legacypass') {
|
||||
return {
|
||||
id,
|
||||
name: '名宿传薪',
|
||||
category: 'major',
|
||||
weight: 0,
|
||||
text: '族中老修行者寿数将至,欲将一生所学倾囊相授一位后进。家祠议定传承对象。',
|
||||
options: [
|
||||
{ label: '按资质择少年传薪', hint: '其后进精进一大截,老修行者气血微亏', eff: { memberBy: { by: 'inspire', target: 'youngest', n: 12 }, flag: { passExchange: true } } },
|
||||
{ label: '传于族中顶梁', hint: '巅峰者再得护持', eff: { memberBy: { by: 'exp', target: 'highestPower', n: 6 } } },
|
||||
{ label: '留待其自然坐化', hint: '声望-2', eff: { rep: -2 } }
|
||||
]
|
||||
}
|
||||
}
|
||||
if (id === 'ev-auction') {
|
||||
return {
|
||||
id,
|
||||
name: '仙门拍卖',
|
||||
category: 'major',
|
||||
weight: 0,
|
||||
text: '季末仙门拍卖会张榜:百水阁将会有灵器与丹材出价。族中是否竞逐?',
|
||||
options: [
|
||||
{ label: '入市竞拍(灵石300)', hint: '可得一件灵器/破境丹/功法', eff: { res: { stones: -300 }, flag: { auctionWin: true } } },
|
||||
{ label: '观望', hint: '声望不损,口袋不破', eff: {} }
|
||||
]
|
||||
}
|
||||
}
|
||||
if (id === 'ev-winterprayer') {
|
||||
return {
|
||||
id,
|
||||
@@ -250,12 +277,34 @@ export function matchesCond(w: World, cond?: Cond): boolean {
|
||||
|
||||
export function eventRoll(w: World): void {
|
||||
const s = w.state
|
||||
if (s.pendingEvent || s.eventQueue.length > 0) {
|
||||
if (!s.pendingEvent && s.eventQueue.length > 0) {
|
||||
fire(w, s.eventQueue.shift()!)
|
||||
if (s.pendingEvent) {
|
||||
if (s.pendingEvent.startsWith('ev-trib-')) {
|
||||
const memberId = s.pendingEvent.replace('ev-trib-', '').split('-')[0]
|
||||
const c = w.state.members[memberId]
|
||||
if (c?.alive) {
|
||||
if (!c.tribDelayYear) {
|
||||
c.tribPendingMonths = (c.tribPendingMonths ?? 0) + 1
|
||||
if ((c.tribPendingMonths ?? 0) >= 12) {
|
||||
// 悬置一年未应:自动压制(防无人/挂机软锁)
|
||||
c.tribDelayYear = w.state.year + 1
|
||||
c.tribPendingMonths = 0
|
||||
w.log('bad', `${c.name} 的天劫悬而未决,只得引气压制,待来年。`)
|
||||
s.pendingEvent = undefined
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.pendingEvent = undefined
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if (s.eventQueue.length > 0) {
|
||||
const evId = s.eventQueue.shift()!
|
||||
const tm = evId.match(/^ev-tournament-(\d+)$/)
|
||||
if (tm) delete s.family.flag[`tourneyPending-${tm[1]}`]
|
||||
fire(w, evId)
|
||||
return
|
||||
}
|
||||
|
||||
// 命运里程碑最优先(百年庆典 > 飞升之路)
|
||||
if (s.year >= 100 && !s.completedEvents.includes('ev-centennial')) {
|
||||
@@ -270,12 +319,38 @@ export function eventRoll(w: World): void {
|
||||
checkApprenticeExpiry(w)
|
||||
recruitCheck(w)
|
||||
|
||||
// 五年一会的太虚大比(该年任一时刻)
|
||||
// 五年一会的太虚大比(该年任一时刻优先;其他年次要事件避让)
|
||||
const tourneyNext = Math.ceil(s.year / 5) * 5
|
||||
if (s.year >= 10 && s.year === tourneyNext && !s.completedEvents.includes(`ev-tournament-${tourneyNext}`)) {
|
||||
fire(w, `ev-tournament-${tourneyNext}`)
|
||||
const tk = `tourneyPending-${tourneyNext}`
|
||||
if (w.state.pendingEvent) {
|
||||
// 另有事件在档:加入等待队列(事件优先)——用 flag 标记,防重复入队
|
||||
if (!s.family.flag[tk] && !s.eventQueue.includes(`ev-tournament-${tourneyNext}`)) {
|
||||
s.family.flag[tk] = true
|
||||
s.eventQueue.unshift(`ev-tournament-${tourneyNext}`)
|
||||
}
|
||||
} else {
|
||||
fire(w, `ev-tournament-${tourneyNext}`)
|
||||
}
|
||||
return
|
||||
}
|
||||
// 名宿传薪(每八年一掷)
|
||||
if (s.year % 8 === 4 && !s.completedEvents.includes('ev-legacypass')) {
|
||||
const elder = w.aliveMembers().some((c) => w.ageOf(c) >= 66 && c.realm.major !== 'mortal')
|
||||
if (elder) {
|
||||
fire(w, 'ev-legacypass')
|
||||
return
|
||||
}
|
||||
}
|
||||
// 仙门拍卖(每年十月掷币)
|
||||
if (s.month === 10 && w.sysEnabled('production')) {
|
||||
const key = `auction-${s.year}`
|
||||
if (!s.family.flag[key] && w.rng.chance(0.55)) {
|
||||
s.family.flag[key] = true
|
||||
fire(w, 'ev-auction')
|
||||
return
|
||||
}
|
||||
}
|
||||
// 冬至岁祷(每年十一月一掷),只在 season 系统开启时
|
||||
if (s.month === 11 && w.sysEnabled('season')) {
|
||||
const key = `prayerDone-${s.year}`
|
||||
@@ -464,9 +539,30 @@ export function applyEffect(w: World, eff: EffectDef, squad?: string[]): void {
|
||||
}
|
||||
delete w.state.family.flag['prayerAsk']
|
||||
}
|
||||
if (eff.flag?.['auctionWin']) {
|
||||
const roll = w.rng.next()
|
||||
if (roll < 0.4) {
|
||||
fam.inventory['weapon-ling'] = (fam.inventory['weapon-ling'] ?? 0) + 1
|
||||
w.log('good', '拍卖会落槌——购得一柄灵器!')
|
||||
} else if (roll < 0.75) {
|
||||
fam.inventory['pill-pojing'] = (fam.inventory['pill-pojing'] ?? 0) + 1
|
||||
w.log('good', '拍卖会落槌——购得一枚破境丹!')
|
||||
} else {
|
||||
const t = w.rng.pick(pack().techniques)
|
||||
if (!fam.techniques.includes(t.id)) {
|
||||
fam.techniques.push(t.id)
|
||||
w.log('good', `拍卖会落槌——竞得《${t.name}》!`)
|
||||
} else {
|
||||
fam.stones += 150
|
||||
w.log('info', '拍卖会灵器已溢价转卖,回款150灵石。')
|
||||
}
|
||||
}
|
||||
delete fam.flag['auctionWin']
|
||||
}
|
||||
if (eff.trib) {
|
||||
const c = w.state.members[eff.trib.memberId]
|
||||
if (c?.alive) {
|
||||
c.tribPendingMonths = 0
|
||||
resolveTribulation(w, c, eff.trib.mode)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -10,7 +10,7 @@ export function yearStartMarriage(w: World): void {
|
||||
const s = w.state
|
||||
const fam = s.family
|
||||
const zongci = fam.buildings['zongci'] ?? 0
|
||||
const birthBase = 0.38 + zongci * 0.03 + (fam.difficulty === 'easy' ? 0.06 : fam.difficulty === 'hard' ? -0.06 : 0)
|
||||
const birthBase = 0.6 + zongci * 0.04 + (fam.difficulty === 'easy' ? 0.06 : fam.difficulty === 'hard' ? -0.06 : 0)
|
||||
|
||||
const couples = buildCouples(w)
|
||||
|
||||
@@ -31,7 +31,7 @@ export function yearStartMarriage(w: World): void {
|
||||
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.03)) {
|
||||
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
|
||||
@@ -79,7 +79,7 @@ export function yearStartMarriage(w: World): void {
|
||||
return age >= 16 && age <= 42
|
||||
})
|
||||
for (const m of men) {
|
||||
if (w.rng.chance(0.28) && women.length > 0) {
|
||||
if (w.rng.chance(0.55) && women.length > 0) {
|
||||
const candidate = women.filter(
|
||||
(x) =>
|
||||
!(x.fatherId && x.fatherId === m.fatherId) &&
|
||||
|
||||
@@ -58,8 +58,8 @@ export function resolveTribulation(w: World, c: Character, mode: 'rash' | 'guard
|
||||
|
||||
// 失败
|
||||
c.lastBreakthroughAttempt = s.year * 12 + s.month
|
||||
c.realmProgress = Math.max(0, 100 - 45 - w.rng.int(0, 15))
|
||||
c.health = Math.max(1, c.health - 30 - w.rng.int(0, 15))
|
||||
c.realmProgress = Math.max(0, 100 - 28 - w.rng.int(0, 12))
|
||||
c.health = Math.max(1, c.health - 22 - w.rng.int(0, 12))
|
||||
if (c.health < 20) c.state = 'wounded'
|
||||
let text = `${c.name} 渡劫失败,肉身受创。`
|
||||
const danger = realmDeathChance(c.realm, c.mind)
|
||||
@@ -85,10 +85,10 @@ export function resolveTribulation(w: World, c: Character, mode: 'rash' | 'guard
|
||||
|
||||
export function perTribChance(w: World, c: Character): number {
|
||||
const base = ({
|
||||
foundation: 0.52,
|
||||
core: 0.42,
|
||||
nascent: 0.3,
|
||||
spirit: 0.2
|
||||
foundation: 0.6,
|
||||
core: 0.5,
|
||||
nascent: 0.36,
|
||||
spirit: 0.26
|
||||
} as Record<string, number>)[c.realm.major] ?? 0.9
|
||||
return Math.min(0.92, base + c.mind * 0.01 + c.health / 400)
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ export interface Character {
|
||||
apprentice?: { sect: string; untilYear: number; quiet: boolean }
|
||||
aspiration?: string
|
||||
tribDelayYear?: number
|
||||
tribPendingMonths?: number
|
||||
}
|
||||
|
||||
export interface FamilyState {
|
||||
|
||||
Reference in New Issue
Block a user