0.1.39 P4: purgeNpc 清理 worldGen.npcs/relations 僵尸定义 + 金钟罩重算 + 4例测试

This commit is contained in:
2026-09-13 13:01:00 +08:00
parent e2070c8fcb
commit fdc9d750f2
3 changed files with 88 additions and 4 deletions
+16 -1
View File
@@ -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 { function purgeNpc(w: World, s: WorldSimState, id: string): void {
delete w.state.npcFamilies[id] delete w.state.npcFamilies[id]
delete s.npcDyn[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)) w.state.eventQueue = (w.state.eventQueue ?? []).filter((e) => !e.startsWith('ev-raid-') || !e.endsWith(id))
if (s.distress?.id === id) s.distress = undefined if (s.distress?.id === id) s.distress = undefined
unregisterNpcDef(id) 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}`) w.emitFx('ripple', `purge:${id}`)
} }
+3 -3
View File
@@ -7,11 +7,11 @@ import { World } from '../src/renderer/game/engine/runtime/World'
* 任何改动(重构日程/调平衡/加系统)若改变了确定性序列或结果,此测试立刻报红。 * 任何改动(重构日程/调平衡/加系统)若改变了确定性序列或结果,此测试立刻报红。
* 更新规则:仅当**有意**变更序列逻辑时,三枚 seed 指纹同版更新并注明原因。 * 更新规则:仅当**有意**变更序列逻辑时,三枚 seed 指纹同版更新并注明原因。
*/ */
// 0.1.37 天人感应基线:因果系统(karma衰减/灾年调制/NPC姿态调制/渡劫调制) + 飞升试炼三阶段 + 世界奇观(种子派生零rng)—— // 0.1.39 淬体·洗髓基线:purgeNpc 清理 worldGen.npcs/relations 僵尸定义(存档体积精简——时序内更改)。
// 受控变更重算(karma入指纹/assessStance karma调制/WorldSim.tick新增D-0.8/D-0.9段)。 // 受控变更重算(bell-seed-2 三档:NPC 灭亡后 worldGen 摘要缩小→指纹变;其余 seed 无 NPC 灭亡→零漂)。
const GOLDEN: Record<string, Record<number, string>> = { const GOLDEN: Record<string, Record<number, string>> = {
'bell-seed-1': { 560: '45b0c17a', 1200: '62e037c1', 2160: 'b85dd7c7' }, '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' } 'bell-seed-3': { 560: 'f24480f4', 1200: 'e52bed85', 2160: 'abb2a5d2' }
} }
+69
View File
@@ -0,0 +1,69 @@
/**
* 0.1.39 P4purgeNpc 清理 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))
})
})