import { describe, expect, it } from 'vitest' import { World } from '../src/renderer/game/engine/runtime/World' import { modParse, modParseAll } from '../src/renderer/game/engine/runtime/modSchema' import { modToPlugin } from '../src/renderer/game/engine/runtime/modManager' import { examplePlugin } from '../src/renderer/game/engine/runtime/demo-plugins' import { eventRoll, findEvent } from '../src/renderer/game/engine/runtime/Systems/events' function mk(seed: string): World { return World.create({ seed, surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' }) } describe('0.1.29 续命·审计歼灭', () => { it('P0-A 人口曲线:28 年后出生持续且稳定 ≤26 人(178 年样本)', () => { const w = mk('pop-x1') const t0 = Date.now() for (let i = 0; i < 2200; i++) { if (w.state.gameOver) break w.advanceMonth() while (w.state.pendingEvent) w.applyEventChoice(w.state.pendingEvent, 0) } const alive = Object.values(w.state.members).filter((c) => c.alive) expect(alive.length).toBeLessThanOrEqual(28) const gens = new Set(alive.map((c) => c.generation)) expect(gens.size).toBeGreaterThanOrEqual(2) expect(Date.now() - t0).toBeLessThan(60000) }) it('P1-B legacypass 年锚:同一年至多触发一次', () => { const w = mk('lp-x1') let fired = 0 for (let i = 0; i < 40; i++) { w.advanceMonth() if (w.state.pendingEvent === 'ev-legacypass') { fired++ w.applyEventChoice(w.state.pendingEvent, 0) } } expect(fired).toBeLessThanOrEqual(1) // 40 月跨 4 年;y%8==4 年只有 1 个 → ≤1 次 }) it('P1-C 敌强度:相对化公式上界 2.6(不再 1.8 封顶撞维)', () => { // 公式:min(2.6, max(0.5, 0.35 + rel*0.55))——rel 大时趋近 2.6,小值向下限 0.5 const relBig = 900 / 60 const strength = Math.min(2.6, Math.max(0.5, 0.35 + relBig * 0.55)) expect(strength).toBeCloseTo(2.6) const relSmall = 60 / 6000 const weak = Math.min(2.6, Math.max(0.5, 0.35 + relSmall * 0.55)) expect(weak).toBeCloseTo(0.5) }) it('P1-O bundle 全量:导出 2 包 → 解析全部 → 逐装成功', () => { const w = mk('bt-x1') const p1 = modParse(JSON.stringify({ id: 'bt-a', name: 'a', version: '1', data: {} })) const p2 = modParse(JSON.stringify({ id: 'bt-b', name: 'b', version: '1', data: {} })) if (!p1.ok || !p2.ok) return expect(false).toBe(true) w.installPlugin(modToPlugin(p1.pack)) w.installPlugin(modToPlugin(p2.pack)) const txt = w.exportModBundle()! const all = modParseAll(txt) expect(all.length).toBe(2) const w2 = mk('bt-x2') for (const p of all) w2.installPlugin(modToPlugin(p)) expect(w2.pluginList().filter((p) => p.id.startsWith('mod-bt-')).length).toBe(2) }) it('P1-V 异常回滚:install 中途炸 → 池/卡全摘', () => { const w = mk('crash-x1') const bad = { ...examplePlugin, id: 'crash-pl', install(ctx: never) { void ctx; throw new Error('boom') } } const r = w.installPlugin(bad as never) expect(r.ok).toBe(false) expect(w.eventPoolIds().includes('crash-pl')).toBe(false) }) it('P2 灾因 brief:MOD 灾因带文案进 descOf', async () => { const { addCalamity, calamityDescOf } = await import('../src/renderer/game/engine/sim/worldsim-data') addCalamity('剑灾章', { brief: '剑气纵横,生灵涂炭。', family: { lingcao: 0.8 } }) expect(calamityDescOf('剑灾章')).toContain('剑气纵横') }) })