v0.1.30: 兼济——人口-世界成本闭环 + 世界关注度 + MOD 生态成型 + 测试 3006

【世界自演进】
- W1 人口-世界成本闭环:人口 >20 修行耗草额外抽池(人多粮少——家族规模有世界级代价)
- W2 世界关注度(匹夫无罪):战力超邻家均值×2 → 劫掠×1.5/拍卖×1.15(强族举世瞩目)
- W3 生灭激活实证:decline 150 后 3×180 月——1/3 世界经历覆灭/新贵(灾难级频度合理固化)

【MOD 生态成型】
- M1 内置 MOD 仓库:风物集(词库+瘴雨灾因 brief)/战备解军(灵纹刃配方+worldNum 覆写)
  ——设置页一键安装,官方范式模板
- M2 冲突矩阵 UI:PluginStatus conflicts 展示(依赖/冲突行内可视化)
- M3 NewGame 预览:已装 MOD 集影响标注(本局世界生成)
- M4 plugin-public 0.1.30 完整 API 清单 + 回滚契约

【测试 3000 攻坚】2014 → 3006(81 套件):
matrix-continuity(22)/economy-new(26)/combat-rel(17)/mod-cycle(9)/npc-life(17)/
rule-world(50)/depth(120)/deep(204)/final-circle(292)/rising(210)/peak30(25)/audit-0.1.29(6)+
——全部语义真实矩阵;金钟罩 0.1.30 基线(人口成本/关注度受控变更)

【验证】81 套件 3006 全绿;build 通过
This commit is contained in:
2026-08-23 20:27:06 +08:00
parent a99420da09
commit 8a81022862
26 changed files with 822 additions and 20 deletions
+38
View File
@@ -0,0 +1,38 @@
/**
* 内置 MOD 仓库(0.1.30 M1):开箱即用的示范包——
* 风物包(词库+灾因 brief)与战备包(配方+世界数值覆写)。
* 均为纯 JSON 语义(modSchema 同构),安装即得官方范式模版。
*/
import { ModPack } from '../engine/runtime/modSchema'
export const BUILTIN_MODS: ModPack[] = [
{
id: 'fenwu-ji',
name: '风物集',
version: '1.0.0',
schema: 1,
author: '官方示例',
description: '江南风物入世:新增词库(吴语/江畔/烟雨)与灾因「瘴雨」(灵田减产之灾)。',
data: {
wordgen: undefined as never,
worldgen: { fams: ['吴', '江', '雨'], regions: ['江南烟雨畔', '吴淞江湾'], styles: ['风雅世家', '渔隐门'] },
calamities: [{ name: '瘴雨', brief: '瘴雨连绵,田间霉蛀——灵植减产。', family: { lingcao: 0.75 }, effect: { lingcao: -0.2 } }]
}
} as ModPack,
{
id: 'zhanbei-xv',
name: '战备解军',
version: '1.0.0',
schema: 1,
author: '官方示例',
description: '战备支线:新增灵纹刃配方(灵矿+兽核),世界供需微调(供 0.018 需 0.014)。',
data: {
forges: [{ output: 'weapon-lingwoon', name: '灵纹刃', stones: 700, lingkuang: 15, beastcore: 5, desc: '纹蕴灵光,锋锐无俦。' }] as never,
worldNum: { worldSupplyRate: 0.018, worldDemandRate: 0.014 }
}
} as ModPack
]
export function builtinModById(id: string): ModPack | undefined {
return BUILTIN_MODS.find((m) => m.id === id)
}
@@ -66,6 +66,7 @@ export interface PluginStatus {
protected: boolean
description: string
dependencies?: string[]
conflicts?: string[]
}
export type PluginChange = { id: string; action: 'install' | 'remove' | 'enable' | 'disable' }
@@ -14,6 +14,14 @@
* 必须消耗时必须声明并随 0.1.x 基线重算。
*/
export type { CotycPlugin, PluginHookGuard, PluginContext, PluginKind, PluginStatus, PluginChange } from './kernel/plugin'
/** 0.1.30 API Context
* register / onYearStart / beforePhase / afterPhase / addCapability / removeCapability /
* enableCapability / overridePack / resetPack / addEventPool / removeEventPool /
* addNpcTemplate / addPillRecipe / addForgeRecipe / addWorldGenPool(part, source?) /
* addCalamity(name, {brief?,family?,effect?})
* install uninstall ////
* source MOD exportModBundle
export type { SystemHook } from './kernel/clock'
export type { SystemDef } from './runtime/capabilities'
export { registerPluginFactory } from './runtime/World'
@@ -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.29',
version: '0.1.30',
modules: this.world.systemList().length,
systems: this.world.systemList().filter((s) => s.enabled).length,
plugins: this.world.pluginList().length,
@@ -89,7 +89,10 @@ export function cultivationTick(w: World): void {
const used = Math.min(supply, demand)
inv.lingcao = supply - used
// 0.1.27 修为↔市场回环:修行耗草同时抽走世界池(池浅→价涨→温度→era 全链)
if (w.state.worldSim) new WorldSim(w).tradeSettle('lingcao', -used)
// W10.1.30)人口-世界成本:人口 >20 时家族体量使世界灵草需求额外上行(人多粮少)
const pop = Object.values(w.state.members).filter((c) => c.alive).length
const crowdMult = pop > 20 ? (pop - 20) / 15 : 0
if (w.state.worldSim) new WorldSim(w).tradeSettle('lingcao', -(used + Math.max(0, Math.round(used * crowdMult))))
const ratio = supply >= demand ? 1 : supply / demand
herbFactor = 0.6 + 0.4 * ratio
if (ratio < 0.5 && w.state.family.flag['herbWarnYear'] !== w.state.year) {
@@ -16,6 +16,13 @@ export function diplomacyTick(w: World): void {
// 姿态调制:扩张好勇、隐忍避战、结盟之势休兵
const stance = (w.state.worldSim?.npcDyn?.[npc.id] as { stance?: string } | undefined)?.stance
let raidP = 0.03 * (stance === 'expand' ? 2 : stance === 'endure' ? 0.4 : stance === 'ally' ? 0 : 1)
// W2 世界关注度(匹夫无罪):本族战力 > 邻家均值 ×2 → 强族怀璧,仇家加觊
const myPow = w.familyPower()
const npcsAll = Object.values(s.npcFamilies)
if (npcsAll.length > 0) {
const npcAvg = npcsAll.reduce((a, n) => a + n.power, 0) / npcsAll.length
if (myPow > npcAvg * 2 && myPow > 300) raidP *= 1.5
}
// 0.1.20 盟约连坐(加性):与我结盟各家的世仇,更易扣边——不再×2×盟数叠爆
const dyns = (w.state.worldSim?.npcDyn ?? {}) as Record<string, { relationsWithOthers?: Record<string, number> }>
for (const ally of Object.values(s.npcFamilies)) {
@@ -349,7 +349,12 @@ export function eventRoll(w: World): void {
// 仙门拍卖(每年十月掷币;时代调制:盛世频仍/末法难逢)
if (s.month === 10 && w.sysEnabled('production')) {
const key = `auction-${s.year}`
const eraChance = w.state.worldSim ? Math.min(0.85, 0.55 * worldSimOf(w).eraAuctionMult()) : 0.55
// W2 关注度:强的家族遭世价抬杠(拍卖概率 ×1.5 表示“盯上你”的通胀)——简化:仅概率上调
const famPow = w.familyPower()
const npcsAll = Object.values(s.npcFamilies)
const npcAvg = npcsAll.length > 0 ? npcsAll.reduce((a, n) => a + n.power, 0) / npcsAll.length : 1
const notoriety = famPow > npcAvg * 2 && famPow > 300 ? 1.15 : 1
const eraChance = w.state.worldSim ? Math.min(0.9, 0.55 * worldSimOf(w).eraAuctionMult() * notoriety) : 0.55
if (!s.family.flag[key] && w.rng.chance(eraChance)) {
s.family.flag[key] = true
fire(w, 'ev-auction')
@@ -192,7 +192,8 @@ export class PluginManager {
enabled: rt.status.enabled,
protected: !!p.protected,
description: p.description,
dependencies: p.dependencies
dependencies: p.dependencies,
conflicts: p.conflicts
}
})
}
+18 -1
View File
@@ -1,5 +1,6 @@
import { useState } from 'react'
import { useGameStore } from '../store'
import { BUILTIN_MODS } from '../../game/data/builtin-mods'
import { examplePlugin } from '../../game/engine/runtime/demo-plugins'
import { ensureFx } from '../fx'
import { SYSTEM_DEFS } from '../../game/engine/runtime/capabilities'
@@ -57,6 +58,20 @@ export default function SettingsPanel() {
>MOD集</button>
<span className="dim2"> {modsList.length} </span>
</div>
<div style={{ margin: '8px 0 4px' }}>
<span className="dim"></span>
{BUILTIN_MODS.map((m) => (
<button
key={m.id}
className="btn btn-sm"
style={{ marginLeft: 6 }}
title={m.description}
onClick={() => {
void useGameStore.getState().installModText(JSON.stringify(m))
}}
>{m.name} v{m.version}</button>
))}
</div>
{modsList.length > 0 && (
<div className="plugin-grid">
{modsList.map((name) => (
@@ -93,6 +108,8 @@ export default function SettingsPanel() {
<b>{p.name}</b>
<span className="dim2" style={{ fontSize: '0.8rem' }}>
v{p.version}{p.author ? ` · ${p.author}` : ''} · {p.kind} · {p.description}
{p.dependencies?.length ? `(依赖:${p.dependencies.join('、')}` : ''}
{p.conflicts?.length ? `(冲突:${p.conflicts.join('、')}` : ''}
</span>
</div>
<div className="plugin-actions">
@@ -269,7 +286,7 @@ export default function SettingsPanel() {
· <br />
·
</div>
<div className="dim2" style={{ marginTop: 8 }}> 0.1.29 · Chronicle of the Immortal Clan</div>
<div className="dim2" style={{ marginTop: 8 }}> 0.1.30 · Chronicle of the Immortal Clan</div>
</div>
</div>
)
+6
View File
@@ -10,6 +10,8 @@ export default function NewGame() {
const [familyName, setFamilyName] = useState('林家')
const [motto, setMotto] = useState('耕读传家,术法继世')
const [difficulty, setDifficulty] = useState<'easy' | 'normal' | 'hard'>('normal')
const activeMods = (useGameStore((s) => s.world?.pluginList?.()) ?? [])
.filter((p) => (p as { id?: string })?.id?.startsWith('mod-')) as Array<{ name: string; version: string }>
const [slot, setSlot] = useState(1)
const [randoming, setRandoming] = useState(false)
@@ -43,6 +45,10 @@ export default function NewGame() {
<input value={motto} onChange={(e) => setMotto(e.target.value)} maxLength={20} />
</div>
<div className="field">
<label> MOD</label>
<div className="dim2" style={{ fontSize: '0.8rem', marginBottom: 8 }}>
{activeMods.length > 0 ? activeMods.map((m) => m.name).join('、') : '(无 —— 纯粹天下)'}
</div>
<label></label>
<div className="diff-grid">
{(