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:
@@ -269,7 +269,7 @@ export default function SettingsPanel() {
|
||||
· 「外交」与四邻结好联姻;仇雠之族隔岁来犯,打得赢名望大涨,打不赢蚀钱伤丁。<br />
|
||||
· 「史书」自动记述繁华与凋零——百年之后,后人翻开这一卷家族志,见代代薪火、历历雪泥。
|
||||
</div>
|
||||
<div className="dim2" style={{ marginTop: 8 }}>版本 0.1.27 · Chronicle of the Immortal Clan</div>
|
||||
<div className="dim2" style={{ marginTop: 8 }}>版本 0.1.28 · Chronicle of the Immortal Clan</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -25,8 +25,8 @@ export default function WorldPanel() {
|
||||
const tide = ws.tide ?? 0.5
|
||||
const tideLabel = tide > 0.9 ? '灵涨' : tide < 0.7 ? '灵衰' : '汐平'
|
||||
const pool = ws.marketPool ?? {}
|
||||
const newsRaw = ws.newsFeed ?? []
|
||||
const news = useMemo(() => [...newsRaw].slice().reverse(), [newsRaw.length])
|
||||
// P0-6 修复:newsFeed 恒定 120 时 length 依赖 memo 永冻结——直接每次 reverse(120 项零成本)
|
||||
const news = [...(ws.newsFeed ?? [])].slice().reverse()
|
||||
const calamity = ws.calamity
|
||||
const calamityLeft = ws.calamityLeft ?? 0
|
||||
const myPower = Object.values(w.state.members)
|
||||
@@ -137,10 +137,7 @@ export default function WorldPanel() {
|
||||
className="btn btn-sm"
|
||||
title="引粥施药,换两家之谊"
|
||||
onClick={() => {
|
||||
const npc = Object.values(w.state.npcFamilies)[0]
|
||||
npc.relation = Math.min(100, npc.relation + 8)
|
||||
w.state.family.stones -= 50
|
||||
w.log('info', `开仓济世(灵石50),${npc.name} 感念恩义,关系+8。`)
|
||||
w.reliefCalamity(50)
|
||||
bump()
|
||||
}}
|
||||
>救济</button>
|
||||
|
||||
@@ -116,9 +116,13 @@ export const useGameStore = create<GameStore>((set, get) => ({
|
||||
const { modParse } = await import('../game/engine/runtime/modSchema')
|
||||
const parsed = modParse(text)
|
||||
if (!parsed.ok) { setNextToast(`MOD 解析失败:${parsed.error}`); return }
|
||||
const { modToPlugin } = await import('../game/engine/runtime/modManager')
|
||||
const { modToPlugin, modPreflight } = await import('../game/engine/runtime/modManager')
|
||||
const st = useGameStore.getState()
|
||||
if (!st.world) { setNextToast('请先进入一局再装 MOD。'); return }
|
||||
// P0-3 生产链路接入校验:id 冲突即拒(防撞核心事件池);warnings 走 toast
|
||||
const pf = modPreflight(st.world, parsed.pack)
|
||||
if (!pf.ok) { setNextToast(`MOD 安装被拒绝:${pf.reason ?? '校验未过'}`); return }
|
||||
if (pf.warnings.length > 0) setNextToast(`MOD 安装提示:${pf.warnings.slice(0, 2).join(';')}`)
|
||||
const res = st.world.installPlugin(modToPlugin(parsed.pack))
|
||||
if (res.ok) {
|
||||
setNextToast(`MOD「${parsed.pack.name}」已装——新开档的世界将受其影响。`)
|
||||
@@ -148,6 +152,18 @@ export const useGameStore = create<GameStore>((set, get) => ({
|
||||
soundOn: true,
|
||||
|
||||
init: async () => {
|
||||
// P1-13 落盘兜底:页面隐藏/关闭时瞬时保存(防节流窗口(≤12tick)丢档)
|
||||
if (typeof window !== 'undefined') {
|
||||
const flush = () => {
|
||||
const st = useGameStore.getState()
|
||||
if (!st.world || st.screen !== 'game') return
|
||||
void Stash.save(st, '隐藏前')
|
||||
}
|
||||
window.addEventListener('beforeunload', flush)
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'hidden') flush()
|
||||
})
|
||||
}
|
||||
const manager = getSlotManager()
|
||||
const slots = await manager.listSlotMetas()
|
||||
set({ slots })
|
||||
@@ -314,7 +330,10 @@ export const useGameStore = create<GameStore>((set, get) => ({
|
||||
set({ pendingEventId: id, pendingEventDef: def })
|
||||
},
|
||||
|
||||
onGameOver: (reason) => set({ gameOverReason: reason, speed: 0, revision: get().revision + 1 }),
|
||||
onGameOver: (reason) => {
|
||||
stopTimer()
|
||||
set({ gameOverReason: reason, revision: get().revision + 1 })
|
||||
},
|
||||
|
||||
onYearPaper: (report) => {
|
||||
if (report.year >= 2) {
|
||||
|
||||
Reference in New Issue
Block a user