v0.1.26: 墨规清辉——MOD 规范范式 + 灾因注册表 + 页面提亮(1047 全绿)

【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 通过
This commit is contained in:
2026-08-23 16:37:11 +08:00
parent 5d9c333c76
commit ba9766e152
16 changed files with 354 additions and 27 deletions
+41
View File
@@ -0,0 +1,41 @@
import { describe, expect, it } from 'vitest'
/** WCAG 相对亮度与对比度(纯函数审计) */
function luminance(hex: string): number {
const m = hex.replace('#', '')
const [r, g, b] = [0, 2, 4].map((i) => parseInt(m.slice(i, i + 2), 16) / 255)
const lin = (c: number) => (c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4))
return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b)
}
function contrast(a: string, b: string): number {
const la = luminance(a), lb = luminance(b)
const [hi, lo] = la > lb ? [la, lb] : [lb, la]
return (hi + 0.05) / (lo + 0.05)
}
describe('0.1.26 页面可读性(对比度审计)', () => {
const BG = '#100d0a'
it('次级文本 (dim/dim2/help-text) 对比度 ≥ 4.5:1', () => {
const cases: Array<[string, string]> = [
['#a99c80', '.dim'],
['#c3b595', '.dim2'],
['#c9b894', '.help-text'],
['#efe4c9', '.feed-item'],
['#e2d3b4', '.tag 文字'] as never
]
for (const [hex, label] of cases) {
const c = contrast(hex, BG)
expect(c, `${label} 对比 ${c.toFixed(2)}`).toBeGreaterThanOrEqual(4.5)
}
})
it('主文本 paper 对比充足(≥8:1', () => {
expect(contrast('#f2e9d4', BG)).toBeGreaterThanOrEqual(8)
})
it('历史暗色 #6d6150 不再被引用(提亮迁移核对)', () => {
const css = require('fs').readFileSync('src/renderer/ui/styles.css', 'utf-8')
expect(css.includes('#6d6150')).toBe(false)
expect(css.includes('#96896e')).toBe(false)
})
})
+52
View File
@@ -4,6 +4,9 @@ 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',
@@ -58,3 +61,52 @@ describe('0.1.25 MOD 系统', () => {
// 世界生成默认池不变——与 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('冲突')
})
})