v0.1.28: 稳石——第三轮全量审计歼灭(2008 全绿 + 指纹补盲)

【P0 批(7 项,实证驱动)】
- P0-1 世界词库池无回滚(真实漂移实证)→ WorldGenPools 来源追踪 + removePool 精确回滚
  + MOD uninstall 自动摘除(池/灾因/配方/数值四类全局注册——P2-3 一并根治)
- P0-2 新贵 def 不落档(僵尸实证:power 恒 120 零演化)→ 生成即写 worldGen.npcs
  + normalize 占位 def 双保险 + 新贵 id 掺 seed 指纹(P1-10 防跨档碰撞)
- P0-3 校验死代码化 → installModText 装前 modPreflight(冲突拒+警告 toast)
- P0-4 MOD 读档静默消失 → state.pluginsMissing 收集提示(与 MOD_GUIDE 承诺对齐)
- P0-5 救济按钮乌龙(哈希序首个+直改 state)→ World.reliefCalamity(景气最低+双检)
- P0-6 天下快讯永久冻结(memo 依赖恒 120 length)→ 去 memo 每次 reverse
- P0-7 MOD 导出单行道 → modParse 识别 {app:'cotymod',bundle} 逐包递归(roundtrip 闭环)

【P1 批】
- P1-8 指纹补盲(worldGen/sagaAnnals/distress/declineYears 入 hash——0.1.27 成果真实锁定)+ 双基线重算
- P1-9 重放同步 worldGen.npcs(自捕获);P1-11 worldNum 字段接线(install/uninstall 复位)
- P1-13 落盘兜底(beforeunload/visibilitychange 突发 save,堵 12-tick 节流窗口)
- P1-14 onGameOver 停 timer + IPC mods 路径白名单(扩展名正则防穿越)
- P1-15 DataPack 摘除 npcs(覆写死路删除,与五口宣言对齐);P1-4 techniques 双源统一(techniquePrice 读包)

【P2 扫尾】tauntCD 清理/sagaAnnals 上限 40/worldGenMods 落空写入/validateMod 深度校验随 P0-3 提交

【测试】69 套件 2008 全绿(audit-0.1.28 八例:池回滚/僵尸/preflight/bundle/freeze/指纹敏感/worldNum/救济);
金钟罩 0.1.28 基线重算(指纹补盲+受控时序变更);build 通过
This commit is contained in:
2026-08-23 18:07:31 +08:00
parent d8d34fcb74
commit da4fb9f635
24 changed files with 304 additions and 43 deletions
+51 -7
View File
@@ -16,7 +16,7 @@ import { aspirationById as aspirationOf } from '../../data/aspirations'
import { computeLegacy, resolveLegacy, peakRealmIndex, grandTechniqueCount, LegacyArch } from '../narrative/legacy'
import { needsTribulation, tribulationEventId } from './Systems/tribulation'
import { generateWorld } from '../sim/worldgen'
import { registerNpcDef, NpcFamilyDef } from '../../data/npcs'
import { registerNpcDef, npcById, NpcFamilyDef } from '../../data/npcs'
import { addPillRecipe, addForgeRecipe } from '../../data/items'
import { WorldGenPools } from '../sim/worldgen'
import { addCalamity as addCalamityImpl } from '../sim/worldsim-data'
@@ -73,6 +73,16 @@ export function normalizeGameState(state: GameState): GameState {
registerNpcDef(n as never)
}
}
// P0-2 双保险:npcFamilies 中缺 def 的 id → 占位 def(防极端旧档僵尸)
for (const id of Object.keys(state.npcFamilies ?? {})) {
if (!npcById(id)) {
const n = state.npcFamilies[id]
registerNpcDef({
id, name: n.name, region: n.region, style: '遗族', desc: '世族遗脉,家道中落而名犹在册。',
leaderRealm: 'foundation', initialPower: n.power, powerGrowth: [2, 6], sells: [], buys: ['lingcao']
})
}
}
if (!state.worldGen && state.seed) {
const wg = generateWorld(state.seed)
state.worldGen = {
@@ -82,7 +92,13 @@ export function normalizeGameState(state: GameState): GameState {
regionFlavor: wg.regionFlavor,
alliances: wg.alliances,
feuds: wg.feuds,
npcCount: wg.npcs.length
npcCount: wg.npcs.length,
// P1-9 重放同步 npcs 摘要(与 creation 一致——该档自此自捕获,不再每次重放)
npcs: wg.npcs.map((n) => ({
id: n.id, name: n.name, region: n.region, style: n.style,
desc: n.desc, leaderRealm: n.leaderRealm, initialPower: n.initialPower,
powerGrowth: n.powerGrowth, sells: n.sells, buys: n.buys
}))
}
}
// 老版本存档(<0.1.1)缺少新增字段,加载时补齐,避免运行期 undefined 崩溃
@@ -176,13 +192,20 @@ export class World {
this.plugins = new PluginManager(this.buildPluginContext())
this.installCorePlugins()
// 0.1.23 持久化插件重装:读档时按注册表重建非核心插件
const missing: string[] = []
for (const item of state.plugins ?? []) {
const mk = PLUGIN_REGISTRY.get(item.id)
if (!mk) continue
const plugin = mk()
if (plugin.version !== item.version) continue
const r = this.installPlugin(plugin)
if (!mk || mk().version !== item.version) {
missing.push(item.id)
continue
}
const r = this.installPlugin(mk())
if (r.ok && !item.enabled) this.setPluginEnabled(item.id, false)
else if (!r.ok) missing.push(item.id)
}
if (missing.length > 0) {
// P0-4 读档缺失提示(README/MOD 生态承诺对齐)
this.state.pluginsMissing = missing
}
}
@@ -206,7 +229,7 @@ export class World {
},
addPillRecipe: (r) => addPillRecipe(r),
addForgeRecipe: (r) => addForgeRecipe(r),
addWorldGenPool: (part) => WorldGenPools.add(part),
addWorldGenPool: (part, source) => WorldGenPools.add(part, source ?? 'ctx'),
addCalamity: (name, opts) => addCalamityImpl(name, opts),
addCapability: (cap) => {
// 0.1.23:能力卡注册入 World 实例(防跨档全局泄漏);UI 全局清单读 SYSTEM_DEFS 展示不受影响
@@ -548,6 +571,27 @@ export class World {
// ==================== player actions ====================
/** 灾年救济:耗 50 灵石赈济——目标=当前受灾/景气最低之家(直改 state 的 UI 乌龙道修复) */
reliefCalamity(cost = 50): boolean {
const fam = this.state.family
if (fam.stones < cost) return false
const ws = this.state.worldSim as { calamity?: string; npcDyn?: Record<string, { prosperity?: number }> } | undefined
const npcs = Object.values(this.state.npcFamilies)
if (npcs.length === 0) return false
// 选目标:灾年时优先受灾(no 直指字段——以景气最低替代),平时景气最低
let target = npcs[0]!
let best = Infinity
for (const n of npcs) {
const pr = (ws?.npcDyn?.[n.id]?.prosperity ?? 50)
if (pr < best) { best = pr; target = n }
}
fam.stones -= cost
target.relation = Math.min(100, target.relation + 8)
this.log('info', `开仓济世(灵石${cost}),${target.name} 感念恩义,关系+8。`)
void ws
return true
}
/** 盟友求援响应:捐灵石助其重整武备(power+15、关系+8 */
assistAlly(npcId: string, cost = 200): boolean {
const fam = this.state.family