结构(旧 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
140 lines
5.0 KiB
TypeScript
140 lines
5.0 KiB
TypeScript
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))('%s:200 年内未因修炼瓶颈灭族', (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)
|
||
})
|
||
})
|