【A 经济闭环(引擎核心)】 - 修为经济化:灵草成修行燃料——练气以上月耗(闭关2/普通1),无草速修0.6~1.0衰减+缺草每年一告 - 炼丹等级化:bonusOf 白名单表达式求值器(extra 单源);craftChance 转正(L5=100%),丹方 PILL_RECIPES(破境丹需丹房3级),失败折半退料 - 炼器筑宝:FORGE_RECIPES(法器/灵器/法宝)灵矿兽核入炉 - 秘灵灵脉:secretQiOf→rollWarbooty 收益门槛(0.5~1.5 折算),掉落按秘境 techGrades 过滤(暗墨林不再出仙典) - NPC 实力参战:raid 强度/风险随 npc.power 浮动(0.5~1.8)——外交成长期博弈 - 灾年落效:CALAMITY_FAMILY 月度减产乘子 + 疫病/兽潮一次性体感 【B 世界活在 UI】 - 天下面板:快讯时间线/行情/灾年/群雄谱(宗主详情+换代数)+ 结盟(setAlliance,关联岁差不降+年利) - 结盟引擎 truly 落地:allied 字段第一次被写值 【C 写史与治理】 - 手写史书:ChroniclePanel 输入条(misc 入史)+ MemberModal 册立家主 + 年报要闻/辞世讣告 + EventModal 收益预览 - C13 破境丹渡劫修正:需渡劫大境界时药力→tribBoost(+12%)入渡劫事件,不再跳三选 - C14 存储防线:loadState 过 migrate;MigrationError(TOO_NEW/未知版本) 醒目提示 - C15 registry 补全(aspirations/formations/season)+ startNewGame facade 复用 bug 【测试】986 全绿(37 套件,新增经济闭环 7 例);金钟罩 0.1.16 基线固化(修为耗草/炼丹/灾害序列变更受控)
118 lines
4.4 KiB
TypeScript
118 lines
4.4 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { World } from '../src/renderer/game/engine/runtime/World'
|
|
import { GameFacade } from '../src/renderer/game/engine/runtime/ApiFacade'
|
|
import { installCoreSystems, buildClock } from '../src/renderer/game/engine/runtime/clocks'
|
|
import { emptyClock } from '../src/renderer/game/engine/runtime/clocks'
|
|
import { CORE_PLUGINS } from '../src/renderer/game/engine/runtime/plugin-bootstrap'
|
|
import { examplePlugin, brokenPlugin } from './helpers/examplePlugin'
|
|
import { stateFingerprint, longRun } from './fingerprint.helper'
|
|
import { findEvent } 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('PluginCore 插件协议', () => {
|
|
it('内置三插件常驻且受保护', () => {
|
|
const w = baseWorld('pl-a')
|
|
const list = w.pluginList()
|
|
expect(list.length).toBe(3)
|
|
for (const p of list) {
|
|
expect(p.installed).toBe(true)
|
|
expect(p.enabled).toBe(true)
|
|
expect(p.protected).toBe(true)
|
|
}
|
|
const ids = list.map((p) => p.id)
|
|
expect(ids).toContain('core-systems')
|
|
expect(ids).toContain('core-data')
|
|
expect(ids).toContain('core-events')
|
|
})
|
|
|
|
it('核心插件不可卸载(保护)', () => {
|
|
const w = baseWorld('pl-b')
|
|
const r = w.removePlugin('core-systems')
|
|
expect(r.ok).toBe(false)
|
|
})
|
|
|
|
it('依赖缺失拒绝安装;依赖满足则放行', () => {
|
|
const w = baseWorld('pl-c')
|
|
expect(w.installPlugin(brokenPlugin).ok).toBe(false)
|
|
expect(w.installPlugin(examplePlugin).ok).toBe(true)
|
|
expect(w.pluginList().length).toBe(4)
|
|
})
|
|
|
|
it('示例安装后:消息池注入生效 + 山神庇佑加值;卸载后两清', () => {
|
|
const w = baseWorld('pl-d')
|
|
w.installPlugin(examplePlugin)
|
|
expect(w.eventPoolIds()).toContain('demo-peaks')
|
|
const x5 = w.state.members['x5']
|
|
x5.realm = { major: 'qi', minor: 1 }
|
|
const p0 = x5.realmProgress
|
|
// 推进到跨年触发年首庇佑
|
|
for (let i = 0; i < 13; i++) w.advanceMonth()
|
|
expect(x5.realmProgress).toBeGreaterThan(p0)
|
|
// 事件注入可被解析(findEvent 命中 demo 池 = 注入生效;确定性校验不赌概率)
|
|
expect(findEvent('ev-demo-guardian', w)).toBeTruthy()
|
|
// 卸载
|
|
expect(w.removePlugin('demo-peaks').ok).toBe(true)
|
|
expect(w.eventPoolIds()).not.toContain('demo-peaks')
|
|
expect(w.pluginList().length).toBe(3)
|
|
})
|
|
|
|
it('remove 后时钟钩子计数恢复(真摘钩)', () => {
|
|
const w = baseWorld('pl-e')
|
|
const before = w.clock.subscriptionCount()
|
|
w.installPlugin(examplePlugin)
|
|
const mounted = w.clock.subscriptionCount()
|
|
expect(mounted).toBe(before + 1)
|
|
w.removePlugin('demo-peaks')
|
|
expect(w.clock.subscriptionCount()).toBe(before)
|
|
})
|
|
|
|
it('安装序确定性:同版本两次世界插件列表一致', () => {
|
|
const a = baseWorld('pl-f')
|
|
const b = baseWorld('pl-f')
|
|
expect(JSON.stringify(a.pluginList())).toBe(JSON.stringify(b.pluginList()))
|
|
})
|
|
|
|
it('默认管线金钟罩不受插件层影响', () => {
|
|
expect(stateFingerprint(longRun('bell-seed-1').state)).toBe('9531969e')
|
|
expect(stateFingerprint(longRun('bell-seed-2').state)).toBe('f2ecc3e1')
|
|
expect(stateFingerprint(longRun('bell-seed-3').state)).toBe('aeefac96')
|
|
})
|
|
|
|
it('facade 插件查询与 about.plugins', () => {
|
|
const w = baseWorld('pl-g')
|
|
const f = new GameFacade(w, 1)
|
|
const pl = f.query('plugins') as { list: { id: string }[] }
|
|
expect(pl.list.length).toBe(3)
|
|
expect(f.about().plugins).toBe(3)
|
|
})
|
|
|
|
it('订阅可捕获 plugin 生命周期事件', () => {
|
|
const w = baseWorld('pl-h')
|
|
const f = new GameFacade(w, 1)
|
|
const events: string[] = []
|
|
const unsub = f.subscribe((e) => {
|
|
if (e.type === 'plugin') events.push((e as { action: string }).action)
|
|
})
|
|
w.installPlugin(examplePlugin)
|
|
w.removePlugin('demo-peaks')
|
|
expect(events).toContain('install')
|
|
expect(events).toContain('remove')
|
|
unsub()
|
|
})
|
|
|
|
it('buildClock 兼容路径仍完整注册(测试/工具用)', () => {
|
|
const clock = buildClock()
|
|
expect(clock.subscriptionCount()).toBeGreaterThanOrEqual(10)
|
|
const empty = emptyClock()
|
|
expect(empty.subscriptionCount()).toBe(0)
|
|
void installCoreSystems
|
|
})
|
|
})
|