From fdc9d750f21b02441f7545bedcbb0bcdbeb8d832 Mon Sep 17 00:00:00 2001 From: thzxx <1440196015@qq.com> Date: Sun, 13 Sep 2026 13:01:00 +0800 Subject: [PATCH] =?UTF-8?q?0.1.39=20P4:=20purgeNpc=20=E6=B8=85=E7=90=86=20?= =?UTF-8?q?worldGen.npcs/relations=20=E5=83=B5=E5=B0=B8=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=20+=20=E9=87=91=E9=92=9F=E7=BD=A9=E9=87=8D=E7=AE=97=20+=204?= =?UTF-8?q?=E4=BE=8B=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/game/engine/sim/WorldSim.ts | 17 +++++- tests/clock.test.ts | 6 +-- tests/worldsim-purge-0.1.39.test.ts | 69 ++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 tests/worldsim-purge-0.1.39.test.ts diff --git a/src/renderer/game/engine/sim/WorldSim.ts b/src/renderer/game/engine/sim/WorldSim.ts index 2eec4dc..66430f8 100644 --- a/src/renderer/game/engine/sim/WorldSim.ts +++ b/src/renderer/game/engine/sim/WorldSim.ts @@ -743,7 +743,10 @@ function evolveNpc(w: World, s: WorldSimState, noise: number): void { } } -/** 覆灭清理:npcFamilies/npcDyn/关系网/raidCD/排队事件/distress/dynamic def 全摘 */ +/** 覆灭清理:npcFamilies/npcDyn/关系网/raidCD/排队事件/distress/dynamic def/worldGen.npcs 全摘 + * 0.1.39 根治:补全 worldGen.npcs + worldGen.relations 中已灭亡 NPC 的定义清理—— + * 旧版 purgeNpc 遗漏了存档层数据,长跑 180 年后 worldGen.npcs 堆积 20+ 条僵尸定义, + * 直接膨胀存档体积且读档时重注册已灭亡 NPC 的 def(虽然无运行时副作用但不洁)。 */ function purgeNpc(w: World, s: WorldSimState, id: string): void { delete w.state.npcFamilies[id] delete s.npcDyn[id] @@ -755,6 +758,18 @@ function purgeNpc(w: World, s: WorldSimState, id: string): void { w.state.eventQueue = (w.state.eventQueue ?? []).filter((e) => !e.startsWith('ev-raid-') || !e.endsWith(id)) if (s.distress?.id === id) s.distress = undefined unregisterNpcDef(id) + // 0.1.39:从 worldGen.npcs 移除已灭亡 NPC 的定义(存档体积精简——僵尸 def 不再落盘) + if (w.state.worldGen?.npcs) { + const idx = w.state.worldGen.npcs.findIndex((n) => n.id === id) + if (idx >= 0) w.state.worldGen.npcs.splice(idx, 1) + } + // 0.1.39:从 worldGen.relations 清理已灭亡 NPC 的关系条目(存档体积精简) + if (w.state.worldGen?.relations) { + delete w.state.worldGen.relations[id] + for (const rid of Object.keys(w.state.worldGen.relations)) { + delete w.state.worldGen.relations[rid]?.[id] + } + } w.emitFx('ripple', `purge:${id}`) } diff --git a/tests/clock.test.ts b/tests/clock.test.ts index 6a0f9be..6f69066 100644 --- a/tests/clock.test.ts +++ b/tests/clock.test.ts @@ -7,11 +7,11 @@ import { World } from '../src/renderer/game/engine/runtime/World' * 任何改动(重构日程/调平衡/加系统)若改变了确定性序列或结果,此测试立刻报红。 * 更新规则:仅当**有意**变更序列逻辑时,三枚 seed 指纹同版更新并注明原因。 */ -// 0.1.37 天人感应基线:因果系统(karma衰减/灾年调制/NPC姿态调制/渡劫调制) + 飞升试炼三阶段 + 世界奇观(种子派生零rng)—— -// 受控变更重算(karma入指纹/assessStance karma调制/WorldSim.tick新增D-0.8/D-0.9段)。 +// 0.1.39 淬体·洗髓基线:purgeNpc 清理 worldGen.npcs/relations 僵尸定义(存档体积精简——时序内更改)。 +// 受控变更重算(bell-seed-2 三档:NPC 灭亡后 worldGen 摘要缩小→指纹变;其余 seed 无 NPC 灭亡→零漂)。 const GOLDEN: Record> = { 'bell-seed-1': { 560: '45b0c17a', 1200: '62e037c1', 2160: 'b85dd7c7' }, - 'bell-seed-2': { 560: '1279a1da', 1200: '3439bd8f', 2160: 'a29efe9f' }, + 'bell-seed-2': { 560: 'be278aba', 1200: '01126e70', 2160: 'fd03f9b2' }, 'bell-seed-3': { 560: 'f24480f4', 1200: 'e52bed85', 2160: 'abb2a5d2' } } diff --git a/tests/worldsim-purge-0.1.39.test.ts b/tests/worldsim-purge-0.1.39.test.ts new file mode 100644 index 0000000..6acaff8 --- /dev/null +++ b/tests/worldsim-purge-0.1.39.test.ts @@ -0,0 +1,69 @@ +/** + * 0.1.39 P4:purgeNpc 清理 worldGen.npcs/relations 僵尸定义测试 + * 验证 NPC 灭亡后 worldGen.npcs 和 worldGen.relations 中的对应条目被同步移除。 + */ +import { describe, expect, it } from 'vitest' +import { World } from '../src/renderer/game/engine/runtime/World' +import { resetSaveBus, attachLogSink } from './world.helpers' +import { stateFingerprint, longRun } from './fingerprint.helper' + +function baseWorld(seed: string): World { + const w = World.create({ seed, surname: '封', familyName: '封家', motto: 'm', difficulty: 'normal' }) + resetSaveBus() + attachLogSink(w) + return w +} + +describe('0.1.39 purgeNpc 存档体积精简', () => { + it('开局 worldGen.npcs 非空且与 npcFamilies 一一对应', () => { + const w = baseWorld('purge-1') + const genNpcs = w.state.worldGen?.npcs ?? [] + const famIds = Object.keys(w.state.npcFamilies) + expect(genNpcs.length).toBe(famIds.length) + for (const n of genNpcs) { + expect(famIds).toContain(n.id) + } + }) + + it('长跑后灭亡 NPC 不残留于 worldGen.npcs', () => { + // 用长跑让 NPC 有机会灭亡(180 年 = 2160 月) + const w = longRun('purge-2', 2160) + if (w.state.gameOver) return // 族灭则跳过 + + const genNpcs = w.state.worldGen?.npcs ?? [] + const famIds = new Set(Object.keys(w.state.npcFamilies)) + + // 所有 worldGen.npcs 中的 id 必须在 npcFamilies 中仍然存活 + for (const n of genNpcs) { + expect(famIds.has(n.id)).toBe(true) + } + // 反过来,所有存活 NPC 必须在 worldGen.npcs 中有定义 + for (const fid of famIds) { + expect(genNpcs.some((n) => n.id === fid)).toBe(true) + } + }) + + it('长跑后灭亡 NPC 不残留于 worldGen.relations', () => { + const w = longRun('purge-3', 2160) + if (w.state.gameOver) return + + const famIds = new Set(Object.keys(w.state.npcFamilies)) + const rels = w.state.worldGen?.relations ?? {} + + // relations 的 key 必须都是存活 NPC + for (const rid of Object.keys(rels)) { + expect(famIds.has(rid)).toBe(true) + // relations 的 value 中的 key 也必须都是存活 NPC + for (const oid of Object.keys(rels[rid] ?? {})) { + expect(famIds.has(oid)).toBe(true) + } + } + }) + + it('确定性:同 seed 同月数 worldGen.npcs 内容一致', () => { + const a = longRun('purge-4', 1200) + const b = longRun('purge-4', 1200) + expect(JSON.stringify(a.state.worldGen?.npcs)).toBe(JSON.stringify(b.state.worldGen?.npcs)) + expect(stateFingerprint(a.state)).toBe(stateFingerprint(b.state)) + }) +})