Files
ChronicleOfTheImmortalClan/tests/longevity.test.ts
T
thzxx ff6df8054c refactor(0.1.14-P1): 内核归位——game/ 按引擎架构重排(零行为漂移)
结构(旧 game/core+engine → 新 engine/ 域):
- engine/kernel/  时钟/随机/插件协议/fxqueue/timesense/format/urgency/guide/names(原 core)
- engine/narrative/  legacy/报告/列传/谱系/年轴(原 core 叙事族)
- engine/runtime/   World/creation/pcgen/ApiFacade/capabilities/pluginManager/boot/clocks + Systems/*(12 系统)
- engine/sim/     Market(未来 WorldSim 同行)
- 旧 game/core、engine/systems、engine/world.ts 等路径全部废弃(无 re-export 兼容层)

验证:35 套件/967 测试全绿(金钟罩三档零漂移=纯搬迁无行为变化)
typecheck 0 error
2026-08-23 13:23:25 +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/runtime/World'
import { applyEventChoice } from '../src/renderer/game/engine/runtime/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)
})
})