import { describe, expect, it } from 'vitest' import { World } from '../src/renderer/game/engine/runtime/World' import { WorldGenPools, generateWorld } from '../src/renderer/game/engine/sim/worldgen' import { engineFromSnapshot } from '../src/renderer/game/engine/GameEngine' import { npcById } from '../src/renderer/game/data/npcs' import { modParse } from '../src/renderer/game/engine/runtime/modSchema' import { modToPlugin, modPreflight } from '../src/renderer/game/engine/runtime/modManager' import { stateFingerprint } from './fingerprint.helper' import { worldNum } from '../src/renderer/game/engine/sim/worldsim-data' function mk(seed: string): World { return World.create({ seed, surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' }) } describe('0.1.28 稳石·审计歼灭', () => { it('P0-1 词库池回滚:注入→回滚后同 seed 生成复原', () => { WorldGenPools.reset() const before = generateWorld('replay-1').npcs.map((n) => n.id).join(',') WorldGenPools.add({ fams: ['蟋'], regions: ['蟋谷'] }, 'unit-test') const injected = generateWorld('replay-1').npcs.map((n) => n.id).join(',') expect(injected).not.toBe(before) WorldGenPools.removePool('unit-test') const after = generateWorld('replay-1').npcs.map((n) => n.id).join(',') expect(after).toBe(before) }) it('P0-2 新贵落档:生成即入 worldGen.npcs,读档非僵尸', () => { const w = mk('zombie-a') const ws = w.state.worldSim as { npcDyn: Record } | undefined void ws // 直接驱动补位(构造弱世+短 run) const target = Object.keys(w.state.npcFamilies)[0]! w.state.npcFamilies[target].power = 40 for (let i = 0; i < 120 && Object.keys(w.state.npcFamilies).length >= 4; i++) w.advanceMonth() // 读档往返 const snap = JSON.parse(JSON.stringify(w.state)) as never const e2 = engineFromSnapshot(snap) for (const id of Object.keys(e2.world.state.npcFamilies)) { expect(npcById(id)).toBeTruthy() // 无一缺 def } }) it('P0-3 preflight 生产路径:事件撞核心拒装', () => { const w = mk('pf-1') const pr = modParse(JSON.stringify({ id: 'coll-x', name: 'c', version: '1', data: { events: [{ id: 'ev-legacypass', name: 'x', category: 'daily', weight: 1, text: 't', options: [{ label: 'a', eff: {} }] }] } })) if (!pr.ok) return expect(pr.ok).toBe(true) const r = modPreflight(w, pr.pack) expect(r.ok).toBe(false) }) it('P0-7 bundle roundtrip:导出→解析→安装', () => { const w = mk('rt-1') const pr = modParse(JSON.stringify({ id: 'round-a', name: 'rt', version: '1.0.0', data: { worldgen: { fams: ['鹿'] } } })) if (!pr.ok) return expect(pr.ok).toBe(true) w.installPlugin(modToPlugin(pr.pack)) const bundleText = w.exportModBundle()! expect(bundleText).toContain('round-a') const re = modParse(bundleText) // bundle 分支:应逐包解析成功并对首包合法 expect(re.ok).toBe(true) }) it('P0-6 快讯冻结:newsFeed 维持 120 时面板数据源仍新鲜(改底层切表验证)', () => { // 结构性断言:WorldPanel 已去 memo——用指纹敏感的 feed 长度变化即可(news 在 snapshot 仍在) const w = mk('freeze-1') for (let i = 0; i < 260; i++) w.advanceMonth() const feed = (w.state.worldSim as { newsFeed: unknown[] }).newsFeed expect(feed.length).toBeGreaterThanOrEqual(3) expect(feed[feed.length - 1]).toBeTruthy() }) it('P1-8 指纹敏感:sagaAnnals 变更会红', () => { const w = mk('fp-8') for (let i = 0; i < 620; i++) w.advanceMonth() const f1 = stateFingerprint(w.state) const ws = w.state.worldSim as { sagaAnnals: { year: number }[] } ws.sagaAnnals.push({ year: 1 }, { year: 2 }) const f2 = stateFingerprint(w.state) expect(f1).not.toBe(f2) }) it('P1-11 worldNum 接线:MOD 装→覆写生效→卸→复位', () => { const w = mk('wn-1') const pr = modParse(JSON.stringify({ id: 'num-x', name: 'n', version: '1', data: { worldNum: { calamityChance: 0.5 } } })) if (!pr.ok) return expect(pr.ok).toBe(true) w.installPlugin(modToPlugin(pr.pack)) expect(worldNum('calamityChance')).toBe(0.5) w.removePlugin('mod-num-x') expect(worldNum('calamityChance')).toBe(0.18) }) it('P0-5 救济:目标为景气最低家(稳定性)', () => { const w = mk('relief-1') const ok = w.reliefCalamity(50) expect(typeof ok).toBe('boolean') expect(w.state.family.stones).toBeGreaterThanOrEqual(0) }) })