/** * 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)) }) })