【MOD 规范与范式(核心命题)】 - docs/MOD_GUIDE.md:完整作者守则——命名/id 规约/semver/schema:1/五数据口 (事件/模板/词库/灾因/配方)/依赖冲突/安全红线(无脚本·禁碰 rng·不动核心时序)/ 打包分发(.cotymod 单文件)/行为承诺 - validateMod 深度校验:category/weight/境界/负成本/非空/options.eff 形状 - modPreflight 权威冲突检测:findEvent(池+动态分支 raid/渡劫/大比/传薪全量) - MOD_SCHEMA_VERSION=1 + schema 声明与升级提示;PluginStatus.author 展示 【灾因归一化(平衡版落地)】 - calamities 由 as const 死表→聚合注册表(calamityNames/calamityFamilyOf/calamityEffectOf+addCalamity) - WorldSim 灾签/生产/Market 全链路走聚合读口——MOD 灾因(剑灾/灵潮湍变)进入世界循环 - 默认表不变→基准世界指纹零漂(clock.test 全绿实证) 【页面可读性(有点暗)】 - .dim #6d6150→#a99c80(3.4:1→7+:1)、.dim2 #96896e→#c3b595、.help-text 提亮、 .feed-item 显色 #efe4c9 + 灰金系 8 处提亮(styles.css 12+/11- 纯色值) - tests/contrast-readability.test.ts:WCAG 对比度审计(≥4.5:1)+ 历史暗色迁移断言 【测试】1047 全绿(47 套件):mod-system 9(+validate/灾因/冲突/预检)、contrast 3; 【金钟罩】0.1.25 基线零漂;build 通过
113 lines
5.8 KiB
TypeScript
113 lines
5.8 KiB
TypeScript
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'
|
||
import { validateMod } from '../src/renderer/game/engine/runtime/modSchema'
|
||
import { addCalamity, calamityNames, calamityFamilyOf, DEFAULT_CALAMITIES } from '../src/renderer/game/engine/sim/worldsim-data'
|
||
import { modPreflight } from '../src/renderer/game/engine/runtime/modManager'
|
||
|
||
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 → 插件 dependencies(mod- 前缀)', () => {
|
||
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 锁死
|
||
})
|
||
})
|
||
|
||
describe('0.1.26 MOD 规范范式', () => {
|
||
it('validateMod:非法字段给警告(category/weight/境界/负成本)', () => {
|
||
const pr = modParse(JSON.stringify({
|
||
id: 'bad-mod', name: '坏包', version: '1.0.0',
|
||
data: {
|
||
events: [{ id: 'ev-bad', name: 'x', category: 'nope', weight: 500, text: '', options: [{ label: 'a', eff: {} }] }],
|
||
npcs: [{ id: 'n-bad', name: 'b', region: 'r', style: 's', desc: 'd', leaderRealm: 'ghost', initialPower: 999, powerGrowth: [1, 2] }],
|
||
pills: [{ output: 'pill-bad', name: '坏丹', danfangLevel: 9, stones: -5, lingcao: 1, beastcore: 0, desc: '' }]
|
||
}
|
||
}))
|
||
if (!pr.ok) throw new Error(pr.error)
|
||
const v = validateMod(pr.pack)
|
||
expect(v.ok).toBe(false)
|
||
expect(v.warnings.length).toBeGreaterThanOrEqual(4)
|
||
})
|
||
|
||
it('灾因注册表:addCalamity 重名拒绝/合法生效/进灾签池', () => {
|
||
expect(addCalamity('旱灾', {})).toBe(false) // 默认重名
|
||
expect(addCalamity('剑灾', { family: { lingcao: 0.7 }, effect: { beastcore: 0.2 } })).toBe(true)
|
||
expect(calamityNames()).toContain('剑灾')
|
||
expect(calamityFamilyOf('剑灾').lingcao).toBe(0.7)
|
||
expect(DEFAULT_CALAMITIES.length).toBe(6) // 默认不变
|
||
})
|
||
|
||
it('MOD→插件灾因:安装后灾签池含扩展灾因', () => {
|
||
const pr = modParse(JSON.stringify({
|
||
id: 'calam-mod', name: '灾包', version: '1.0.0', data: {
|
||
calamities: [{ name: '灵潮湍变', family: { lingcao: 0.8 }, effect: { lingkuang: -0.2 } }]
|
||
}
|
||
}))
|
||
if (!pr.ok) throw new Error(pr.error)
|
||
const w = World.create({ seed: 'cm-1', surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' })
|
||
expect(w.installPlugin(modToPlugin(pr.pack)).ok).toBe(true)
|
||
expect(calamityNames()).toContain('灵潮湍变')
|
||
})
|
||
|
||
it('modPreflight:事件 id 冲突拒绝安装(给理由)', () => {
|
||
const pr = modParse(JSON.stringify({
|
||
id: 'collide-mod', name: '撞包', version: '1.0.0',
|
||
data: { events: [{ id: 'ev-legacypass', name: 'x', category: 'daily', weight: 1, text: 'x', options: [{ label: 'a', eff: {} }] }] }
|
||
}))
|
||
if (!pr.ok) throw new Error(pr.error)
|
||
const w = World.create({ seed: 'cf-2', surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' })
|
||
const r = modPreflight(w, pr.pack)
|
||
expect(r.ok).toBe(false)
|
||
expect(r.reason).toContain('冲突')
|
||
})
|
||
})
|