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:
@@ -42,6 +42,8 @@ export interface PluginContext {
|
||||
addForgeRecipe: (r: { output: string; name: string; stones: number; lingkuang: number; beastcore: number; desc: string }) => void
|
||||
/** 世界生成词库池注入(styles/regions/fams/suffixes) */
|
||||
addWorldGenPool: (part: { styles?: string[]; regions?: string[]; fams?: string[]; suffixes?: string[] }) => void
|
||||
/** 灾因注入(0.1.26:进入世界灾签循环;重名拒绝 false) */
|
||||
addCalamity: (name: string, opts: { family?: Partial<Record<string, number>>; effect?: Partial<Record<string, number>> }) => boolean
|
||||
addCapability: (cap: { id: string; name: string; version: string; desc: string }) => void
|
||||
removeCapability: (id: string) => void
|
||||
enableCapability: (id: string, enabled: boolean) => void
|
||||
@@ -55,6 +57,7 @@ export interface PluginStatus {
|
||||
id: string
|
||||
name: string
|
||||
version: string
|
||||
author?: string
|
||||
kind: PluginKind
|
||||
installed: boolean
|
||||
enabled: boolean
|
||||
|
||||
@@ -137,7 +137,7 @@ export class GameFacade {
|
||||
about(): { title: string; version: string; modules: number; systems: number; plugins: number; packFingerprint: string } {
|
||||
return {
|
||||
title: '仙途家族志',
|
||||
version: '0.1.25',
|
||||
version: '0.1.26',
|
||||
modules: this.world.systemList().length,
|
||||
systems: this.world.systemList().filter((s) => s.enabled).length,
|
||||
plugins: this.world.pluginList().length,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { World } from '../World'
|
||||
import { aspirationById } from '../../../data/aspirations'
|
||||
import { seasonMod } from '../../../data/season'
|
||||
import { CALAMITY_FAMILY } from '../../sim/worldsim-data'
|
||||
import { calamityFamilyOf } from '../../sim/worldsim-data'
|
||||
|
||||
export function productionTick(w: World): void {
|
||||
const fam = w.state.family
|
||||
@@ -19,9 +19,9 @@ export function productionTick(w: World): void {
|
||||
const merchants = w.aliveMembers().filter((c) => aspirationById(c.aspiration)?.effect.type === 'market').length
|
||||
const springMod = w.sysEnabled('season') ? seasonMod(w.state.month, 'field') : 0
|
||||
// 灾年减产(CALAMITY_FAMILY 乘量)
|
||||
const cl = w.state.worldSim?.calamity as (keyof typeof CALAMITY_FAMILY) | undefined
|
||||
const farmFactor = cl ? (CALAMITY_FAMILY[cl]?.lingcao ?? 1) : 1
|
||||
const mineFactor = cl ? (CALAMITY_FAMILY[cl]?.lingkuang ?? 1) : 1
|
||||
const cl = w.state.worldSim?.calamity as string | undefined
|
||||
const farmFactor = cl ? (calamityFamilyOf(cl).lingcao ?? 1) : 1
|
||||
const mineFactor = cl ? (calamityFamilyOf(cl).lingkuang ?? 1) : 1
|
||||
if (lingtian > 0) {
|
||||
const v = Math.round(10 * lingtian * (1 + fielders * 0.05 + springMod) * farmFactor)
|
||||
inv.lingcao = (inv.lingcao ?? 0) + v
|
||||
|
||||
@@ -19,6 +19,7 @@ import { generateWorld } from '../sim/worldgen'
|
||||
import { registerNpcDef, NpcFamilyDef } from '../../data/npcs'
|
||||
import { addPillRecipe, addForgeRecipe } from '../../data/items'
|
||||
import { WorldGenPools } from '../sim/worldgen'
|
||||
import { addCalamity as addCalamityImpl } from '../sim/worldsim-data'
|
||||
import { fire } from './Systems/events'
|
||||
import { createWorldState, findInheritor } from './creation'
|
||||
import { SYSTEM_DEFS, SystemDef } from './capabilities'
|
||||
@@ -206,6 +207,7 @@ export class World {
|
||||
addPillRecipe: (r) => addPillRecipe(r),
|
||||
addForgeRecipe: (r) => addForgeRecipe(r),
|
||||
addWorldGenPool: (part) => WorldGenPools.add(part),
|
||||
addCalamity: (name, opts) => addCalamityImpl(name, opts),
|
||||
addCapability: (cap) => {
|
||||
// 0.1.23:能力卡注册入 World 实例(防跨档全局泄漏);UI 全局清单读 SYSTEM_DEFS 展示不受影响
|
||||
if (!self.systems[cap.id]) self.systems[cap.id] = { enabled: true }
|
||||
|
||||
@@ -3,7 +3,22 @@
|
||||
* 全量复用 PluginManager 管线:持久化/启停/双闸/自动回滚。
|
||||
*/
|
||||
import { CotycPlugin } from '../kernel/plugin'
|
||||
import { ModPack } from './modSchema'
|
||||
import { ModPack, validateMod } from './modSchema'
|
||||
import { EventDef } from '../../data/events'
|
||||
import { findEvent } from './Systems/events'
|
||||
import { World } from './World'
|
||||
|
||||
/** 安装前检查:事件 id 与核心/已装池冲突 + 范式校验警告 */
|
||||
export function modPreflight(w: World, pack: ModPack): { ok: boolean; reason?: string; warnings: string[] } {
|
||||
const v = validateMod(pack)
|
||||
// 权威冲突检测:findEvent 即「池 + 动态分支」(raid/渡劫/大比/传薪/百年…)全量
|
||||
for (const e of pack.data.events ?? []) {
|
||||
if (findEvent(e.id, w)) {
|
||||
return { ok: false, reason: `事件 id 冲突:${e.id}`, warnings: v.warnings }
|
||||
}
|
||||
}
|
||||
return { ok: true, warnings: v.warnings }
|
||||
}
|
||||
|
||||
export function modToPlugin(pack: ModPack): CotycPlugin {
|
||||
return {
|
||||
@@ -25,6 +40,10 @@ export function modToPlugin(pack: ModPack): CotycPlugin {
|
||||
// 配方补充
|
||||
for (const r of pack.data.pills ?? []) ctx.addPillRecipe(r)
|
||||
for (const r of pack.data.forges ?? []) ctx.addForgeRecipe(r)
|
||||
// 灾因补充(0.1.26:MOD 灾因进入世界循环)
|
||||
for (const c of pack.data.calamities ?? []) {
|
||||
ctx.addCalamity(c.name, { family: c.family, effect: c.effect })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ export interface ModPackData {
|
||||
fams?: string[]
|
||||
suffixes?: string[]
|
||||
}
|
||||
/** 灾因扩展(0.1.26:进入世界灾签循环) */
|
||||
calamities?: Array<{ name: string; family?: Record<string, number>; effect?: Record<string, number> }>
|
||||
/** 丹方/铸器配方 */
|
||||
pills?: Array<{ output: string; name: string; danfangLevel: number; stones: number; lingcao: number; beastcore: number; desc: string }>
|
||||
forges?: Array<{ output: string; name: string; stones: number; lingkuang: number; beastcore: number; desc: string }>
|
||||
@@ -30,6 +32,8 @@ export interface ModPack {
|
||||
gameVersion?: string
|
||||
author?: string
|
||||
description?: string
|
||||
/** 模式范式版本(0.1.26 起建议声明 = 1) */
|
||||
schema?: number
|
||||
depends?: string[]
|
||||
conflicts?: string[]
|
||||
data: ModPackData
|
||||
@@ -73,3 +77,44 @@ export function modParse(text: string): ModParseResult {
|
||||
}
|
||||
return { ok: true, pack: raw as ModPack }
|
||||
}
|
||||
|
||||
export const MOD_SCHEMA_VERSION = 1
|
||||
|
||||
export interface ModValidation {
|
||||
ok: boolean
|
||||
warnings: string[]
|
||||
}
|
||||
|
||||
const CATEGORIES = ['daily', 'major', 'fate'] as const
|
||||
const REALMS = ['mortal', 'qi', 'foundation', 'core', 'nascent', 'spirit'] as const
|
||||
|
||||
/** 范式校验(0.1.26 规范):深度字段检查——event 形状/模板境界/配方非负 */
|
||||
export function validateMod(pack: ModPack): ModValidation {
|
||||
const warnings: string[] = []
|
||||
if (pack.schema !== undefined && pack.schema !== MOD_SCHEMA_VERSION) {
|
||||
warnings.push(`schema 版本 ${pack.schema} ≠ 当前支持 ${MOD_SCHEMA_VERSION}(提示 MOD 作者更新)。`)
|
||||
}
|
||||
if (!pack.author) warnings.push('缺 author(建议填,供玩家辨识)。')
|
||||
for (const e of pack.data.events ?? []) {
|
||||
if (!CATEGORIES.includes(e.category as never)) warnings.push(`事件 ${e.id} category 非法(daily|major|fate)。`)
|
||||
if (!(e.weight >= 0 && e.weight <= 100)) warnings.push(`事件 ${e.id} weight 超界(0~100)。`)
|
||||
if (!e.text || !e.text.trim()) warnings.push(`事件 ${e.id} 缺正文。`)
|
||||
if (!e.options || e.options.length === 0) warnings.push(`事件 ${e.id} 缺选项。`)
|
||||
for (const o of e.options ?? []) {
|
||||
if (!o.label) warnings.push(`事件 ${e.id} 某选项缺 label。`)
|
||||
if (!o.eff || typeof o.eff !== 'object') warnings.push(`事件 ${e.id} 某选项缺 eff(可空对象)。`)
|
||||
}
|
||||
}
|
||||
for (const n of pack.data.npcs ?? []) {
|
||||
if (!REALMS.includes(n.leaderRealm)) warnings.push(`模板 ${n.id} leaderRealm 非法(${REALMS.join('|')})。`)
|
||||
if (!(n.initialPower >= 60 && n.initialPower <= 400)) warnings.push(`模板 ${n.id} initialPower 建议 60~400。`)
|
||||
}
|
||||
for (const r of pack.data.pills ?? []) {
|
||||
if ([r.stones, r.lingcao, r.beastcore].some((v) => v < 0)) warnings.push(`丹方 ${r.output} 含负成本。`)
|
||||
if (!(r.danfangLevel >= 1 && r.danfangLevel <= 5)) warnings.push(`丹方 ${r.output} danfangLevel 建议 1~5。`)
|
||||
}
|
||||
for (const r of pack.data.forges ?? []) {
|
||||
if ([r.stones, r.lingkuang, r.beastcore].some((v) => v < 0)) warnings.push(`配方 ${r.output} 含负成本。`)
|
||||
}
|
||||
return { ok: warnings.length === 0, warnings }
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ export class PluginManager {
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
version: p.version,
|
||||
author: p.author,
|
||||
kind: p.kind,
|
||||
installed: rt.status.installed,
|
||||
enabled: rt.status.enabled,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** WorldSim —— 世界自进化引擎(game/engine/sim/WorldSim.ts) */
|
||||
import { World } from '../runtime/World'
|
||||
import { WorldSimState, NpcDynamics, WORLDSIM, ERA_CONF, ERA_DURA, makeWorldSimState, NpcStance, REGION_TIDE, CALAMITY_EFFECT, CALAMITY_FAMILY, CalamityName, POOL_BASE } from './worldsim-data'
|
||||
import { WorldSimState, NpcDynamics, WORLDSIM, ERA_CONF, ERA_DURA, makeWorldSimState, NpcStance, REGION_TIDE, calamityNames, calamityFamilyOf, calamityEffectOf, CalamityName, POOL_BASE } from './worldsim-data'
|
||||
import { pack } from '../../data/registry'
|
||||
import { ITEMS } from '../../data/items'
|
||||
import { npcById, getNpcDefs, registerNpcDef, unregisterNpcDef } from '../../data/npcs'
|
||||
@@ -89,7 +89,7 @@ export class WorldSim {
|
||||
if ((s.calamityLeft ?? 0) > 0) s.calamityLeft = (s.calamityLeft ?? 0) - 1
|
||||
const calamityP = WORLDSIM.calamityChance * ERA_CONF[s.era ?? 'pingshi'].calamityMult
|
||||
if (this.w.state.month === 1 && rng.chance(calamityP)) {
|
||||
const cl = rng.pick([...WORLDSIM.calamities])
|
||||
const cl = rng.pick(calamityNames())
|
||||
s.calamity = cl
|
||||
s.calamityYear = this.w.state.year
|
||||
s.calamityLeft = WORLDSIM.calamityMonths
|
||||
@@ -409,7 +409,7 @@ function inv(w: World): Record<string, number> {
|
||||
}
|
||||
|
||||
function applyCalamityToMarket(s: WorldSimState, cl: CalamityName): void {
|
||||
const eff = CALAMITY_EFFECT[cl]
|
||||
const eff = calamityEffectOf(cl)
|
||||
for (const [id, pct] of Object.entries(eff) as [string, number][]) {
|
||||
s.marketPool[id] = Math.max(10, (s.marketPool[id] ?? 50) * (1 + pct))
|
||||
}
|
||||
|
||||
@@ -155,10 +155,13 @@ export const WORLDSIM = {
|
||||
newsKeep: 120
|
||||
}
|
||||
|
||||
export type CalamityName = (typeof WORLDSIM.calamities)[number]
|
||||
export type CalamityName = string
|
||||
|
||||
/** 默认灾因集(聚合读口:默认+MOD/插件扩展——默认表和不变 → 基准世界指纹不漂) */
|
||||
export const DEFAULT_CALAMITIES = ['旱灾', '涝灾', '蝗灾', '疫病', '兽潮', '寒潮'] as const
|
||||
|
||||
/** 灾因 → 家族实际生产乘量(1 = 正常) */
|
||||
export const CALAMITY_FAMILY: Record<CalamityName, Partial<Record<string, number>>> = {
|
||||
export const CALAMITY_FAMILY: Record<string, Partial<Record<string, number>>> = {
|
||||
旱灾: { lingcao: 0.7 },
|
||||
涝灾: { lingcao: 0.8, lingkuang: 0.95 },
|
||||
蝗灾: { lingcao: 0.55 },
|
||||
@@ -168,7 +171,7 @@ export const CALAMITY_FAMILY: Record<CalamityName, Partial<Record<string, number
|
||||
}
|
||||
|
||||
/** 灾因 → 资源方向 */
|
||||
export const CALAMITY_EFFECT: Record<CalamityName, Partial<Record<string, number>>> = {
|
||||
export const CALAMITY_EFFECT: Record<string, Partial<Record<string, number>>> = {
|
||||
旱灾: { lingcao: -0.3 },
|
||||
涝灾: { lingcao: -0.2, lingkuang: -0.1 },
|
||||
蝗灾: { lingcao: -0.45 },
|
||||
@@ -176,3 +179,34 @@ export const CALAMITY_EFFECT: Record<CalamityName, Partial<Record<string, number
|
||||
兽潮: { beastcore: 0.3 },
|
||||
寒潮: { lingcao: -0.2, beastcore: -0.15 }
|
||||
}
|
||||
|
||||
/** 扩展灾因注册表(MOD/插件 addCalamity)——名字唯一;与默认集聚合 */
|
||||
const EXTENDED_CALAMITIES: string[] = []
|
||||
const EXTENDED_FAMILY: Record<string, Partial<Record<string, number>>> = {}
|
||||
const EXTENDED_EFFECT: Record<string, Partial<Record<string, number>>> = {}
|
||||
|
||||
export function addCalamity(
|
||||
name: string,
|
||||
opts: { family?: Partial<Record<string, number>>; effect?: Partial<Record<string, number>> }
|
||||
): boolean {
|
||||
if (DEFAULT_CALAMITIES.includes(name as never) || EXTENDED_CALAMITIES.includes(name)) return false
|
||||
EXTENDED_CALAMITIES.push(name)
|
||||
if (opts.family) EXTENDED_FAMILY[name] = opts.family
|
||||
if (opts.effect) EXTENDED_EFFECT[name] = opts.effect
|
||||
return true
|
||||
}
|
||||
|
||||
/** 聚合读口(WorldSim 灾签/体感/生产/Market 读取) */
|
||||
export function calamityNames(): string[] {
|
||||
return [...DEFAULT_CALAMITIES, ...EXTENDED_CALAMITIES]
|
||||
}
|
||||
export function calamityFamilyOf(name: string): Partial<Record<string, number>> {
|
||||
return EXTENDED_FAMILY[name] ?? CALAMITY_FAMILY[name] ?? {}
|
||||
}
|
||||
export function calamityEffectOf(name: string): Partial<Record<string, number>> {
|
||||
return EXTENDED_EFFECT[name] ?? CALAMITY_EFFECT[name] ?? {}
|
||||
}
|
||||
/** 灾因中文描述(MOD 灾因缺省用名本身) */
|
||||
export function calamityDescOf(name: string): string {
|
||||
return `${name}——灵植减产,市价将行。`
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export default function SettingsPanel() {
|
||||
<div className="plugin-info">
|
||||
<b>{p.name}</b>
|
||||
<span className="dim2" style={{ fontSize: '0.8rem' }}>
|
||||
v{p.version} · {p.kind} · {p.description}
|
||||
v{p.version}{p.author ? ` · ${p.author}` : ''} · {p.kind} · {p.description}
|
||||
</span>
|
||||
</div>
|
||||
<div className="plugin-actions">
|
||||
@@ -256,7 +256,7 @@ export default function SettingsPanel() {
|
||||
· 「外交」与四邻结好联姻;仇雠之族隔岁来犯,打得赢名望大涨,打不赢蚀钱伤丁。<br />
|
||||
· 「史书」自动记述繁华与凋零——百年之后,后人翻开这一卷家族志,见代代薪火、历历雪泥。
|
||||
</div>
|
||||
<div className="dim2" style={{ marginTop: 8 }}>版本 0.1.25 · Chronicle of the Immortal Clan</div>
|
||||
<div className="dim2" style={{ marginTop: 8 }}>版本 0.1.26 · Chronicle of the Immortal Clan</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
+12
-11
@@ -209,7 +209,7 @@ html[data-blade] .battle-lines {
|
||||
.btn {
|
||||
background: linear-gradient(180deg, #20180f, #191209);
|
||||
border: 1px solid #3d3221;
|
||||
color: #d8c8a8;
|
||||
color: #e6d8ba;
|
||||
padding: 6px 14px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
@@ -295,11 +295,11 @@ html[data-blade] .battle-lines {
|
||||
}
|
||||
|
||||
.dim {
|
||||
color: #6d6150;
|
||||
color: #a99c80;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.dim2 {
|
||||
color: #96896e;
|
||||
color: #c3b595;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.good {
|
||||
@@ -474,7 +474,7 @@ html[data-blade] .battle-lines {
|
||||
}
|
||||
.boot-en {
|
||||
letter-spacing: 5px;
|
||||
color: #7d6c4a;
|
||||
color: #a4916c;
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
.boot-divider {
|
||||
@@ -580,7 +580,7 @@ html[data-blade] .battle-lines {
|
||||
}
|
||||
.newgame .card-title {
|
||||
font-size: 1.6rem;
|
||||
color: #d9bd85;
|
||||
color: #e6cd9a;
|
||||
letter-spacing: 10px;
|
||||
}
|
||||
.field {
|
||||
@@ -590,7 +590,7 @@ html[data-blade] .battle-lines {
|
||||
}
|
||||
.field label {
|
||||
width: 80px;
|
||||
color: #b09f7c;
|
||||
color: #c4b28a;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
.field input,
|
||||
@@ -622,7 +622,7 @@ html[data-blade] .battle-lines {
|
||||
border: 1px solid #3a3021;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
color: #cbbd9e;
|
||||
color: #ddceac;
|
||||
transition: all 0.16s;
|
||||
}
|
||||
.diff-opt:hover {
|
||||
@@ -744,7 +744,7 @@ html[data-blade] .battle-lines {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
color: #cbbd9e;
|
||||
color: #ddceac;
|
||||
background: rgba(38, 29, 16, 0.6);
|
||||
border: 1px solid #2f2517;
|
||||
padding: 3px 9px;
|
||||
@@ -943,6 +943,7 @@ html[data-blade] .battle-lines {
|
||||
border-left: 2px solid transparent;
|
||||
margin-bottom: 3px;
|
||||
font-size: 0.85rem;
|
||||
color: #efe4c9;
|
||||
line-height: 1.5;
|
||||
border-radius: 2px;
|
||||
}
|
||||
@@ -1092,7 +1093,7 @@ html[data-blade] .battle-lines {
|
||||
}
|
||||
|
||||
.help-text {
|
||||
color: #a2906c;
|
||||
color: #c9b894;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.8;
|
||||
max-width: 680px;
|
||||
@@ -1358,7 +1359,7 @@ html[data-blade] .battle-lines {
|
||||
.opt-eff {
|
||||
display: block;
|
||||
font-size: 0.74rem;
|
||||
color: rgba(150, 132, 96, 0.9);
|
||||
color: rgba(196, 178, 140, 0.96);
|
||||
margin-top: 3px;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
@@ -1884,7 +1885,7 @@ html[data-blade] .battle-lines {
|
||||
padding: 3px 10px;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
color: #cbbd9e;
|
||||
color: #ddceac;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.world-badge:hover {
|
||||
|
||||
Reference in New Issue
Block a user