Files
ChronicleOfTheImmortalClan/tests/plugin.test.ts
T
thzxx 2f62eecb08 v0.1.18: 时代绘卷 + 特效入引擎(双主线完成,1002 测试破千)
【主线一:世界自演进深化《时代绘卷》】
- 世纪弧 era 状态机:盛世/平世/乱世/末法四态(ERA_CONF 全参数化)。
  灾年概率×0.5~1.6、世界供需×0.7~1.25、拍卖概率×0.6~1.3、tide 偏置 ±0.09;
  ERA_DURA 控制转换节奏(15~45年),转移时史官快讯+log。世界有大节律了
- NPC 景气仓廪:npcDyn.prosperity(8~100)月结余——买卖+0.5~0.8、断供-2、灾年-3;
  >75 景气 power+1、<30 衰闷 power-1;换代焕新(55~75)。NPC 在过日子
- 蝴蝶效应:互攻胜率 power 加权(强者恒强)、打残广播(败家邻望-8/胜家+4);
  玩家劫掠胜利/结盟直接写 NPC·NPC 关系网(-6/-3)——玩家行动推进世界历史
- 天下十年一鉴:每十年史官综述(灾/换代/行情/灵潮聚合,kind='annal')
- 灾年↔秘境:灾年灵脉恢复减半(末法感)
- WorldPanel 天下志:era 徽标(悬停描述)/史官十年鉴/群雄势衡柱状图/衰微标注

【主线二:特效系统并入引擎(架构升级)】
- WorldEventBus.onFx + Kernel 转发 + World.emitFx 语义发射(零 rng 消耗)
- 12 个语义发射点:突破/化神/渡劫/劫掠胜负/遭遇战/拍卖/大比/灾年起散/换代
  —— 取代 UI log 文本正则猜测(spark/blade 双源锁死)
- makeBus 桥接 FxGate(kind→gate.emit),gate 挡位/密度/reduced 逻辑保留在 UI

【测试】1002 全绿(40 套件):fxqueue-semantic 3 例 + era-world 5 例;
金钟罩 0.1.18 基线固化(era/prosperity/蝴蝶效应受控变更)
2026-08-23 14:38:27 +08:00

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('d07ebe85')
expect(stateFingerprint(longRun('bell-seed-2').state)).toBe('3fcec396')
expect(stateFingerprint(longRun('bell-seed-3').state)).toBe('ecdc62da')
})
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
})
})