v0.1.23: 生灭千秋 + 插件生态化第一课(1027 全绿)

【世界模型:世界会死人也会新生】
- 家族生灭:declineYears(power<58+景气<38 连续8年)→ 覆灭(最强邻分食+史官广播)+ 新贵补位(6%/年,乱世×2)
  —— NPC 永远四家铁打的现状终结;玩家补刀(raid×0.82)可加速衰亡
- npcById 双源可空(静态+动态注册表;不再 throw——15 处消费者防御化)
- purgeNpc 全局清理(raidCD/事件队列/恩怨网/distress/动态 def)
- 秘境产出回池(loot 20% 经 tradeSettle 回流)——资源循环最后单向封口
- WorldSnapshot 只读摘要门面(UI/史书消费,零行为变更)
- 潮汐长波(10 年正弦 ±0.08 叠加,灵潮三十年河东)

【插件生态化(0.1.8 后第一次大进化)】
- 持久化:GameState.plugins 落盘 + PLUGIN_REGISTRY 读档重装(engineFromSnapshot 真实路径验证)
- plugin-public.ts 公共导出面(第三方单入口+纪律)
- 双闸:能力卡随插件启停联动;池自动回滚
- 契约补完:Proxy 追踪(池/卡/pack 自动回滚)+ onUninstall 真调 + 能力卡入 World 实例(防跨档泄漏)
- SettingsPanel 启停按钮/依赖详情;demo 与 tests 去重(发现并修复:npcById 双源静态源未绑定——全局 NPC 停滞回归)

【测试】1027 全绿(44 套件):dynasty-plugin 7 例(覆灭/新贵/回池/快照/持久化/双闸/自动回滚);
金钟罩 0.1.23 基线固化;build 通过
This commit is contained in:
2026-08-23 16:03:01 +08:00
parent d2787ee3c8
commit 04ef80faa5
27 changed files with 455 additions and 98 deletions
@@ -166,6 +166,7 @@ export function resolveRaid(
): EncounterResult {
const npc = w.state.npcFamilies[npcId]
const def = npcById(npcId)
if (!def) return { win: false, draw: true, lines: ['此族已散。'], loot: undefined, losses: [] }
// 宗主实力分层:power 由年度成长+换代驱动,劫掠强度随之浮动(0.5↔1.8)
const npcPower = npc.power ?? 60
const strength = Math.min(1.8, Math.max(0.5, 0.5 + (npcPower / 120) * 0.5))
@@ -193,7 +194,7 @@ export function resolveRaid(
w.state.family.reputation += 6
// B5:战争伤骨——败方 power 重挫,次年不再来犯(warCooldownYear/raidCount 启用)
w.emitFx('blade', `raid:${npcId}`)
npc.power = Math.max(40, Math.round(npc.power * 0.82))
npc.power = Math.max(52, Math.round(npc.power * 0.82))
npc.raidCount = (npc.raidCount ?? 0) + 1
npc.warCooldownYear = w.state.year
// 1-3 蝴蝶效应:玩家重创名声——邻家对败者关系趋冷
@@ -36,6 +36,7 @@ export function yearGrowth(w: World): void {
const s = w.state
for (const npc of Object.values(s.npcFamilies)) {
const def = npcById(npc.id)
if (!def) continue
const [a, b] = def.powerGrowth
npc.power += w.rng.int(a, b)
if (npc.allied) {
@@ -29,6 +29,7 @@ export function dynamicEventFor(id: string, w?: World): EventDef | undefined {
if (id.startsWith('ev-raid-')) {
const npcId = id.replace('ev-raid-', '')
const npc = npcById(npcId)
if (!npc) return undefined // 该族已散(覆灭/附庸)——事件自然消亡
return {
id,
name: `${npc.name}来犯`,
@@ -70,6 +70,15 @@ export function missionTick(w: World): void {
m.result = 'success'
releaseSquad(w, m)
const total = rollWarbooty(w, def.completionLoot, qiRatioOf(w, def.id))
// 0.1.23 秘境回池:探得之物近 2 成回流人间(负反馈世界侧供给)
if (w.state.worldSim) {
const sim = new WorldSim(w)
for (const [k, v] of Object.entries(total)) {
if (k === 'tech') continue
const bleed = Math.max(1, Math.round((v as number) * 0.2))
sim.tradeSettle(k === 'stones' ? 'lingkuang' : k, bleed)
}
}
m.log.push(`凯旋而归,清点战利:${lootText(total)}`)
const survivors = squadOf(w, m).filter((c) => c.alive).map((c) => c.name).join('、')
w.chronicle(