评估发现(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 通过
95 lines
3.1 KiB
TypeScript
95 lines
3.1 KiB
TypeScript
import type { World } from '../world'
|
|
import { Character } from '../../types/domain'
|
|
import { nextRealm, MAJOR_ORDER, realmDeathChance, describeRealm } from '../../data/realms'
|
|
|
|
export const TRIB_MAJORS: string[] = ['foundation', 'core', 'nascent', 'spirit']
|
|
|
|
export function needsTribulation(c: Character): boolean {
|
|
const next = nextRealm(c.realm)
|
|
if (!next) return false
|
|
if (next.major === c.realm.major) return false
|
|
return TRIB_MAJORS.includes(next.major)
|
|
}
|
|
|
|
export function tribulationEventId(c: Character): string {
|
|
return `ev-trib-${c.id}-${c.realm.major}-${c.realm.minor}`
|
|
}
|
|
|
|
export function resolveTribulation(w: World, c: Character, mode: 'rash' | 'guard' | 'delay'): string {
|
|
const next = nextRealm(c.realm)
|
|
if (!next) return 'peak'
|
|
|
|
if (mode === 'delay') {
|
|
c.tribDelayYear = w.state.year + 1
|
|
w.log('info', `${c.name} 按兵不动,引而不发,待来年再渡。`)
|
|
return 'delayed'
|
|
}
|
|
|
|
let successChance = perTribChance(w, c)
|
|
let guardian: Character | null = null
|
|
if (mode === 'guard') {
|
|
const fam = w.state.family
|
|
if (fam.stones < 100) {
|
|
w.log('bad', '护法之资不足,此行只得咬牙亲渡。')
|
|
} else {
|
|
fam.stones -= 100
|
|
successChance += 0.08
|
|
guardian = w
|
|
.aliveMembers()
|
|
.filter((m) => m.id !== c.id && MAJOR_ORDER.indexOf(m.realm.major) >= MAJOR_ORDER.indexOf(c.realm.major))
|
|
.sort((a, b) => MAJOR_ORDER.indexOf(b.realm.major) - MAJOR_ORDER.indexOf(a.realm.major))[0] ?? null
|
|
}
|
|
}
|
|
|
|
const p = Math.min(0.92, Math.max(0.06, successChance))
|
|
const s = w.state
|
|
if (w.rng.chance(p)) {
|
|
c.realm = next
|
|
c.realmProgress = 0
|
|
c.health = 100
|
|
c.lastBreakthroughAttempt = s.year * 12 + s.month
|
|
w.chronicle('breakthrough', `${c.name} 渡劫功成,踏入【${describeRealm(next)}】!`, c.id, true)
|
|
w.log('good', `✦ 天雷散尽,${c.name} 渡劫成功,晋阶【${describeRealm(next)}】!`)
|
|
if (next.major === 'spirit' && !s.flags['firstSpirit']) {
|
|
s.flags['firstSpirit'] = s.year
|
|
}
|
|
return 'success'
|
|
}
|
|
|
|
// 失败
|
|
c.lastBreakthroughAttempt = s.year * 12 + s.month
|
|
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)
|
|
if (w.rng.chance(danger)) {
|
|
c.alive = false
|
|
c.deathYear = s.year
|
|
c.deathCause = '渡劫陨落'
|
|
w.chronicle('death', `${c.name} 天劫加身,灵石俱焚而陨。`, c.id, true)
|
|
w.log('bad', `☠ ${c.name} 渡劫陨落。`)
|
|
return 'dead'
|
|
}
|
|
if (guardian) {
|
|
const g = guardian
|
|
if (w.rng.chance(0.5)) {
|
|
g.health = Math.max(1, g.health - 15 - w.rng.int(0, 15))
|
|
if (g.health < 25) g.state = 'wounded'
|
|
w.log('bad', `${g.name} 为护法所伤。`)
|
|
}
|
|
}
|
|
w.log('bad', text)
|
|
return 'fail'
|
|
}
|
|
|
|
export function perTribChance(w: World, c: Character): number {
|
|
const base = ({
|
|
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)
|
|
}
|