Files
ChronicleOfTheImmortalClan/tests/longevity.test.ts
T
thzxx 586fa643b1 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 通过
2026-08-23 11:13:48 +08:00

140 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest'
import { World } from '../src/renderer/game/engine/world'
import { applyEventChoice } from '../src/renderer/game/engine/systems/events'
import { resetSaveBus, attachLogSink } from './world.helpers'
const SEEDS = ['long-a', 'long-b', 'long-c']
function runYearWithActions(w: World, years: number): void {
const months = years * 12
for (let i = 0; i < months; i++) {
if (w.state.gameOver) break
if (w.state.pendingEvent) {
applyEventChoice(w, w.state.pendingEvent, 0)
w.state.pendingEvent = undefined
}
w.advanceMonth()
}
}
function highestMajor(w: World): number {
const order = ['mortal', 'qi', 'foundation', 'core', 'nascent', 'spirit']
let max = 0
for (const c of Object.values(w.state.members)) {
if (!c.alive) continue
const idx = order.indexOf(c.realm.major)
if (idx > max) max = idx
}
return max
}
describe('长线存活验收(玩家代理)', () => {
it.each(SEEDS.map((s) => [s] as const))('%s200 年内未因修炼瓶颈灭族', (seed) => {
const w = World.create({ seed, surname: '岳', familyName: '岳家', motto: 'm', difficulty: 'normal' })
resetSaveBus()
attachLogSink(w)
runYearWithActions(w, 200)
const alive = Object.values(w.state.members).filter((c) => c.alive).length
// 若灭族,必须发生在非常晚期(>120Y),且修炼已达高位——即非"卡死"
if (w.state.gameOver) {
expect(w.state.gameOver.year).toBeGreaterThan(120)
}
expect(alive).toBeGreaterThanOrEqual(0)
})
it.each(SEEDS.map((s) => [s] as const))('%s:有成员达到筑基或以上(修炼不卡死)', (seed) => {
const w = World.create({ seed, surname: '岳', familyName: '岳家', motto: 'm', difficulty: 'normal' })
resetSaveBus()
attachLogSink(w)
runYearWithActions(w, 120)
expect(highestMajor(w)).toBeGreaterThanOrEqual(2)
})
it('玩家代理 300 年内不卡在炼气(存在高境界成员或已登高位后陨落)', () => {
const w = World.create({ seed: 'long-g', surname: '岳', familyName: '岳家', motto: 'm', difficulty: 'normal' })
resetSaveBus()
attachLogSink(w)
runYearWithActions(w, 300)
const reached = w.state.chronicle.some((e) => e.category === 'breakthrough' && /筑基|金丹|元婴|化神/.test(e.text))
if (w.state.gameOver) {
expect(reached).toBe(true) // 若灭族,必曾登高位——非修炼卡死
} else {
expect(highestMajor(w)).toBeGreaterThanOrEqual(1)
}
})
})
describe('渡劫悬置回退', () => {
it('悬置 12 月自动压制(挂机不软锁)', () => {
const w = World.create({ seed: 'trib-hang', surname: '袁', familyName: '袁家', motto: 'm', difficulty: 'normal' })
resetSaveBus()
attachLogSink(w)
const c = w.state.members['x5']
c.realm = { major: 'qi', minor: 8 }
c.realmProgress = 100
c.mind = 9
c.perception = 9
let suppressedOnce = false
for (let i = 0; i < 30; i++) {
w.advanceMonth()
// 压制发生时事件被清 & delay 被设
if (!w.state.pendingEvent && c.tribDelayYear && (c.tribDelayYear ?? 0) >= 1 && !c.tribPendingMonths) suppressedOnce = true
if (c.tribDelayYear === undefined && !c.tribPendingMonths && !w.state.pendingEvent?.startsWith('ev-trib-')) {
// 可能刚 fire 于本月
}
}
// 至少发生过一次压制(不软锁)
expect(suppressedOnce).toBe(true)
})
it('玩家处理渡劫后确实晋升(qi8→筑基)', () => {
const w = World.create({ seed: 'trib-handle', surname: '袁', familyName: '袁家', motto: 'm', difficulty: 'normal' })
resetSaveBus()
attachLogSink(w)
const c = w.state.members['x5']
c.realm = { major: 'qi', minor: 8 }
c.realmProgress = 100
c.mind = 9
c.perception = 9
for (let i = 0; i < 60; i++) {
if (w.state.pendingEvent) {
applyEventChoice(w, w.state.pendingEvent, 0) // 选"硬渡"
w.state.pendingEvent = undefined
}
w.advanceMonth()
if (c.realm.major === 'foundation') break
}
expect(c.realm.major).toBe('foundation')
})
})
describe('拍卖与传薪', () => {
it('拍卖三分支统一不破', () => {
for (let i = 0; i < 10; i++) {
const w = World.create({ seed: `auc-${i}`, surname: '封', familyName: '封家', motto: 'm', difficulty: 'normal' })
resetSaveBus()
attachLogSink(w)
applyEventChoice(w, 'ev-auction', 0)
expect(w.state.family.stones).toBeGreaterThanOrEqual(500)
}
})
it('名宿传薪事件可触发', () => {
const w = World.create({ seed: 'legacy-x', surname: '危', familyName: '危家', motto: 'm', difficulty: 'normal' })
w.state.year = 12
const c = w.state.members['x1']
c.bornYear = 1 - 70
c.realm = { major: 'foundation', minor: 1 }
let fired = false
for (let i = 0; i < 48; i++) {
if (w.state.pendingEvent === 'ev-legacypass') {
fired = true
break
}
if (w.state.pendingEvent) w.state.pendingEvent = undefined
w.advanceMonth()
}
expect(fired).toBe(true)
})
})