结构(旧 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
191 lines
6.9 KiB
TypeScript
191 lines
6.9 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { World } from '../src/renderer/game/engine/runtime/World'
|
|
import { computeLegacy, resolveLegacy, peakRealmIndex, grandTechniqueCount } from '../src/renderer/game/engine/narrative/legacy'
|
|
import { runTournament, buildApprentice, finalizeApprentice, apprenticeCandidates } from '../src/renderer/game/engine/runtime/Systems/tournament'
|
|
import { applyEventChoice } from '../src/renderer/game/engine/runtime/Systems/events'
|
|
import { resetSaveBus, attachLogSink } from './world.helpers'
|
|
|
|
function baseWorld(seed: string): World {
|
|
const w = World.create({ seed, surname: '纪', familyName: '纪家', motto: 'm', difficulty: 'normal' })
|
|
resetSaveBus()
|
|
attachLogSink(w)
|
|
return w
|
|
}
|
|
|
|
describe('legacy 望气评分', () => {
|
|
it('四维分随面板数据单调增(声望峰)', () => {
|
|
const w = baseWorld('leg-s1')
|
|
const d0 = computeLegacy(w.state)
|
|
w.state.stats.repPeak = 78
|
|
const d1 = computeLegacy(w.state)
|
|
expect(d1.weiMing).toBeGreaterThan(d0.weiMing)
|
|
})
|
|
|
|
it('peakRealmIndex 与 grandTechniqueCount 计算正确', () => {
|
|
const w = baseWorld('leg-s2')
|
|
w.state.members['x4'].realm = { major: 'core', minor: 0 }
|
|
expect(peakRealmIndex(w.state.members)).toBe(30)
|
|
w.state.members['x3'].techniqueRank = 2
|
|
expect(grandTechniqueCount(w.state.members)).toBe(1)
|
|
})
|
|
|
|
it('十二称号分档边界正确', () => {
|
|
const w = baseWorld('leg-s3')
|
|
w.state.stats.feishengCount = 1
|
|
expect(resolveLegacy(w.state).title).toBe('飞升之资')
|
|
w.state.stats.feishengCount = 0
|
|
// 强制高分档 150+
|
|
w.state.stats.repPeak = 80
|
|
w.state.stats.maxRealmIdx = 60 // 化神
|
|
w.state.stats.popPeak = 45
|
|
w.state.stats.techniqueGrand = 12
|
|
w.state.year = 250
|
|
const arch = resolveLegacy(w.state)
|
|
expect(['仙朝遗脉', '中州望族']).toContain(arch.title)
|
|
})
|
|
|
|
it('极弱家族落入冢中枯骨档', () => {
|
|
const w = baseWorld('leg-s4')
|
|
w.state.stats.repPeak = 0
|
|
w.state.stats.maxRealmIdx = -1
|
|
w.state.stats.popPeak = 1
|
|
w.state.year = 3
|
|
w.state.stats.feishengCount = 0
|
|
expect(resolveLegacy(w.state).title).toBe('冢中枯骨')
|
|
})
|
|
|
|
it('定局落印单次生效且写入史书', () => {
|
|
const w = baseWorld('leg-s5')
|
|
const c0 = w.state.chronicle.length
|
|
const arch = w.resolveLegacyNow()
|
|
expect(arch.title).toBeTruthy()
|
|
expect(w.state.family.flag['resolved']).toBe(true)
|
|
expect(w.state.stats.resolvedYear).toBe(w.state.year)
|
|
expect(w.state.stats.resolveTitle).toBe(arch.title)
|
|
expect(w.state.chronicle.length).toBe(c0 + 1)
|
|
})
|
|
})
|
|
|
|
describe('tournament 太虚大比', () => {
|
|
it('十年(year%5=0 & year>=10)触发征召事件', () => {
|
|
const w = baseWorld('tr-a')
|
|
w.state.year = 9
|
|
w.state.month = 12
|
|
w.advanceMonth()
|
|
expect(w.state.pendingEvent).toBe('ev-tournament-10')
|
|
w.state.pendingEvent = undefined
|
|
w.advanceMonth()
|
|
expect(w.state.pendingEvent).toBe('ev-tournament-10') // 已火后完成列表保护内一月再次 fire
|
|
// 完成记入后不再
|
|
w.state.completedEvents.push('ev-tournament-10')
|
|
w.state.pendingEvent = undefined
|
|
w.advanceMonth()
|
|
expect(w.state.pendingEvent).not.toBe('ev-tournament-10')
|
|
})
|
|
|
|
it('点将赛出:三关全胜计入 rank1 与声望红利', () => {
|
|
const w = baseWorld('tr-b')
|
|
w.state.members['x3'].realm = { major: 'foundation', minor: 0 }
|
|
runTournament(w, ['x3'])
|
|
const rec = w.state.stats.tourneyHistory[w.state.stats.tourneyHistory.length - 1]
|
|
expect(rec).toBeTruthy()
|
|
expect(rec!.year).toBe(w.state.year)
|
|
expect(rec!.rank).toBeGreaterThanOrEqual(1)
|
|
expect(rec!.rank).toBeLessThanOrEqual(4)
|
|
expect(w.state.battles.length).toBeGreaterThanOrEqual(1)
|
|
})
|
|
|
|
it('无人可遣时告假缺席不崩溃', () => {
|
|
const w = baseWorld('tr-c')
|
|
for (const c of Object.values(w.state.members)) {
|
|
c.state = 'apprentice'
|
|
}
|
|
runTournament(w)
|
|
expect(w.state.stats.tourneyHistory.length).toBe(0)
|
|
})
|
|
|
|
it('称病不出选项:声望-3 且有最低记账', () => {
|
|
const w = baseWorld('tr-d')
|
|
w.state.year = 10
|
|
const rep0 = w.state.family.reputation
|
|
applyEventChoice(w, 'ev-tournament-10', 2) // 称病
|
|
expect(w.state.family.reputation).toBe(rep0 - 3)
|
|
})
|
|
})
|
|
|
|
describe('apprentice 宗门寄读', () => {
|
|
it('招生条件与派遣', () => {
|
|
const w = baseWorld('ap-a')
|
|
w.state.members['x5'].bornYear = 1 - 15 // 15 岁
|
|
const cands = apprenticeCandidates(w)
|
|
expect(cands.length).toBeGreaterThan(0)
|
|
buildApprentice(w)
|
|
const apprentice = Object.values(w.state.members).find((c) => c.state === 'apprentice')
|
|
expect(apprentice).toBeTruthy()
|
|
expect(apprentice!.apprentice?.untilYear).toBeGreaterThan(w.state.year)
|
|
})
|
|
|
|
it('寄读者不修炼不婚配', () => {
|
|
const w = baseWorld('ap-b')
|
|
const c = w.state.members['x5']
|
|
c.bornYear = 1 - 15
|
|
buildApprentice(w)
|
|
const p0 = c.realmProgress
|
|
w.advanceMonth()
|
|
expect(c.realmProgress).toBe(p0)
|
|
// candidates 不含
|
|
expect(w.marriageCandidatesOf('x3').map((x) => x.id)).not.toContain(c.id)
|
|
})
|
|
|
|
it('到期归家:境界推进与状态复原', () => {
|
|
const w = baseWorld('ap-c')
|
|
const c = w.state.members['x3']
|
|
c.bornYear = 1 - 30
|
|
c.state = 'apprentice'
|
|
c.apprentice = { sect: '云栖宗', untilYear: 1, quiet: false }
|
|
finalizeApprentice(w, c.id, 'return')
|
|
expect(c.state).toBe('idle')
|
|
expect(c.apprentice).toBeUndefined()
|
|
// 归来必定取得进步:要么修为涨,要么已突破
|
|
const advanced = c.realmProgress >= 60 || c.realm.minor > 1 || c.realm.major !== 'qi'
|
|
expect(advanced).toBe(true)
|
|
})
|
|
|
|
it('续读再延二年', () => {
|
|
const w = baseWorld('ap-d')
|
|
const c = w.state.members['x3']
|
|
c.state = 'apprentice'
|
|
c.apprentice = { sect: '太谷书院', untilYear: 5, quiet: false }
|
|
finalizeApprentice(w, c.id, 'stay')
|
|
expect(c.apprentice?.untilYear).toBe(Math.max(5, w.state.year + 2))
|
|
expect(c.state).toBe('apprentice')
|
|
})
|
|
|
|
it('寄读期满自动推事件', () => {
|
|
const w = baseWorld('ap-e')
|
|
const c = w.state.members['x3']
|
|
c.state = 'apprentice'
|
|
c.apprentice = { sect: '丹霞洞天', untilYear: w.state.year, quiet: false }
|
|
w.advanceMonth()
|
|
const queued = w.state.eventQueue.some((id) => id.startsWith('ev-apprentice-'))
|
|
expect(queued).toBe(true)
|
|
// smoke保证 year%4 的招生在年份匹配时也 fire(不影响上述)
|
|
})
|
|
})
|
|
|
|
describe('seekSutra 求经台', () => {
|
|
it('藏书阁>=3 才可、两年冷却、收获入藏', () => {
|
|
const w = baseWorld('sk-a')
|
|
w.state.family.buildings = { cangshu: 2 }
|
|
expect(w.seekSutra()).toBe(false)
|
|
w.state.family.buildings = { cangshu: 3 }
|
|
w.state.family.stones = 1000
|
|
const before = w.state.family.techniques.length
|
|
expect(w.seekSutra()).toBe(true)
|
|
expect(w.state.family.techniques.length).toBeGreaterThanOrEqual(before)
|
|
expect(w.seekSutra()).toBe(false) // 冷却
|
|
w.state.year += 2
|
|
expect(w.seekSutra()).toBe(true)
|
|
})
|
|
})
|