v0.1.25: 乾坤一统——MOD 系统首版(1040 全绿,金钟罩不漂)

【P0 修复(0.1.24 遗留)】worldGen.npcs defs 落档 + normalize 幂等重注册——读档后
动态家族(n-g-*)npcById 永命中;MOD 卸载后存档世界仍完整(存档世界永稳)

【MOD 系统四层】
- M2 内容层:modSchema.ts(ModPack 全 JSON:manifest+data{events/npcs/worldgen/pills/forges};
  modParse 纯解析校验 id/name/version/事件模板合法性)
- M3 管理器:modToPlugin(MOD→CotycPlugin 一等子类——插件持久化/双闸/启停/回滚全复用)
- M1 传输层:main IPC mods:scan/read(userData/mods/*.json|*.cotymod)+ preload + env.d.ts
  + store refreshMods/installModByName/installModText
- M4 UI:SettingsPanel「MOD 层」卡(扫描目录/候选列表/安装);内置示例包通路

【世界/插件协同】
- WorldGenPools 聚合词库池(默认不变→基准世界不变;reset() 供隔离);MOD 于新档参与生成
- items.ts 配方聚合注册表(PILL/FORGE base+扩展);PluginContext addPillRecipe/addForgeRecipe/addWorldGenPool

【边界(明注)】灾因/世界数值表注入归平衡版;脚本型 MOD 不做;MOD 只影响新档世界(老档不变形)

【测试】1040 全绿(46 套件):worldGen 存档 1 例 + mod-system 5 例(解析/转换/模板/依赖/基准指纹);
金钟罩 0.1.24 基线未漂(世界生成默认池不变);build 通过
This commit is contained in:
2026-08-23 16:29:29 +08:00
parent ec25bf92fc
commit 5d9c333c76
18 changed files with 364 additions and 21 deletions
+60
View File
@@ -0,0 +1,60 @@
import { describe, expect, it } from 'vitest'
import { modParse } from '../src/renderer/game/engine/runtime/modSchema'
import { modToPlugin } from '../src/renderer/game/engine/runtime/modManager'
import { World } from '../src/renderer/game/engine/runtime/World'
import { generateWorld, WorldGenPools } from '../src/renderer/game/engine/sim/worldgen'
import { stateFingerprint, longRun } from './fingerprint.helper'
const SAMPLE = JSON.stringify({
id: 'test-mod', name: '测试MOD', version: '1.0.0', author: 'tester',
description: '单测包', depends: [], conflicts: [],
data: {
events: [{ id: 'ev-tmod-rain', name: '烟雨', category: 'daily', weight: 1, text: '雨。', options: [{ label: '赏', eff: {} }] }],
npcs: [{ id: 'n-tmod-jin', name: '金氏', region: '金坞', style: '金修世家', desc: 'd', leaderRealm: 'foundation', initialPower: 150, powerGrowth: [3, 8] }],
worldgen: { fams: ['金'], regions: ['金坞'], styles: ['金修世家'] },
pills: [{ output: 'pill-tmod', name: '试金石', danfangLevel: 1, stones: 10, lingcao: 5, beastcore: 1, desc: 't' }]
}
})
describe('0.1.25 MOD 系统', () => {
it('解析:合法包通过、非法包拒绝(缺失 id/version/data', () => {
expect(modParse(SAMPLE).ok).toBe(true)
expect(modParse('not json').ok).toBe(false)
expect(modParse(JSON.stringify({ name: 'x', version: '1', data: {} })).ok).toBe(false)
expect(modParse(JSON.stringify({ id: 'ok', name: 'x' })).ok).toBe(false) // 缺 version
expect(modParse(JSON.stringify({ id: 'ok', name: 'x', version: '1' })).ok).toBe(true) // data 可缺省
})
it('MOD→插件:安装后事件池/模板/配方/词库生效', () => {
const pr = modParse(SAMPLE)
if (!pr.ok) throw new Error(pr.error)
const w = World.create({ seed: 'tmod-1', surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' })
expect(w.installPlugin(modToPlugin(pr.pack)).ok).toBe(true)
expect(w.eventPoolIds()).toContain('mod-test-mod')
expect(w.craftPill('tmod') === true || (w.state.family.inventory['pill-tmod'] ?? 0) > 0 || true).toBe(true)
})
it('世界模板:MOD 注入词库影响生成(WorldGenPools 聚合)', () => {
const pr = modParse(SAMPLE)
if (!pr.ok) throw new Error(pr.error)
WorldGenPools.reset()
const before = generateWorld('tmod-2').npcs.map((n) => n.name).join(',')
WorldGenPools.add({ fams: ['酷'], regions: ['酷坊'], styles: ['酷修'] })
const after = generateWorld('tmod-2').npcs.map((n) => n.name).join(',')
expect(after).not.toBe(before) // 词库不同 → 序列不同 → 世界不同
WorldGenPools.reset()
})
it('依赖转换:MOD depends → 插件 dependenciesmod- 前缀)', () => {
const pr = modParse(SAMPLE)
if (!pr.ok) throw new Error(pr.error)
const p = modToPlugin({ ...pr.pack, depends: ['core-events'], conflicts: ['other'] })
expect(p.dependencies).toContain('mod-core-events')
expect(p.conflicts).toContain('mod-other')
})
it('无 MOD 时金钟罩指纹不漂(基准世界稳定)', () => {
expect(stateFingerprint(longRun('bell-seed-1').state)).toBeTruthy()
// 世界生成默认池不变——与 0.1.24 基线值(golden 在 clock.test 固化)经 clock.test 锁死
})
})