import { describe, expect, it } from 'vitest' import { World } from '../src/renderer/game/engine/runtime/World' import { WorldSim } from '../src/renderer/game/engine/sim/WorldSim' import { buyItem, sellItem, marketPrice, buyTechnique, techniquePrice } from '../src/renderer/game/engine/sim/Market' import { resolveRaid } from '../src/renderer/game/engine/runtime/Systems/combat' import { POOL_BASE } from '../src/renderer/game/engine/sim/worldsim-data' function worldWithSim(seed: string): World { const w = World.create({ seed, surname: '万', familyName: '万家', motto: 'm', difficulty: 'normal' }) w.advanceMonth() w.state.totalTicks = 60 return w } describe('0.1.17 世界真炉膛', () => { it('买卖回写池:买浅卖盈,断供拒单', () => { const w = worldWithSim('w1') const sim = new WorldSim(w) const p0 = sim.marketMultFor('lingcao') expect(buyItem(w, 'lingcao', 5)).toBe(true) const p1 = sim.marketMultFor('lingcao') expect(p1).toBeLessThan(p0) // 买浅 → 价涨 expect(sellItem(w, 'lingcao', 5)).toBe(true) const p2 = sim.marketMultFor('lingcao') expect(p2).toBeGreaterThan(p1) // 卖盈 → 价跌回来 // 断供:池压平后拒单 const ws = w.state.worldSim as { marketPool: Record } ws.marketPool['lingcao'] = POOL_BASE.lingcao * 0.3 const before = w.state.family.inventory['lingcao'] ?? 0 expect(buyItem(w, 'lingcao', 1)).toBe(false) expect(w.state.family.inventory['lingcao'] ?? 0).toBe(before) }) it('市场呼吸有界:长跑 600 月池不枯竭亦不冲顶', () => { const w = World.create({ seed: 'w2', surname: '安', familyName: '安家', motto: 'm', difficulty: 'normal' }) for (let i = 0; i < 600; i++) w.advanceMonth() const ws = w.state.worldSim as { marketPool: Record } for (const id of Object.keys(POOL_BASE)) { expect(ws.marketPool[id]).toBeGreaterThan(POOL_BASE[id] * 0.1) expect(ws.marketPool[id]).toBeLessThanOrEqual(POOL_BASE[id] * 2.5) } }) it('NPC 上桌:长期活跃于池(需料者逢底而购)', () => { const w = World.create({ seed: 'w3', surname: '风', familyName: '风家', motto: 'm', difficulty: 'normal' }) for (let i = 0; i < 240; i++) w.advanceMonth() const ws = w.state.worldSim as { marketPool: Record } const beast = ws.marketPool['beastcore'] expect(beast).toBeGreaterThan(POOL_BASE.beastcore * 0.12) expect(beast).toBeLessThan(POOL_BASE.beastcore * 2.4) }) it('劫掠胜利:NPC 伤筋动骨(power-18%、raidCount/warCooldownYear 写值)', () => { const w = worldWithSim('w4') const npcId = Object.keys(w.state.npcFamilies)[0]! const npc = w.state.npcFamilies[npcId] npc.power = 200 const team = [w.state.members['x1']!, w.state.members['x2']!, w.state.members['x3']!, w.state.members['x4']!] for (const c of team) { c.realm = { major: 'spirit', minor: 0 } c.health = 100 } const res = resolveRaid(w, npcId, team) expect(res.win).toBe(true) expect(npc.power).toBeLessThan(200) expect(npc.raidCount).toBeGreaterThan(0) expect(npc.warCooldownYear).toBe(w.state.year) }) it('灾年持续 6 月并散去(B12)', () => { const w = worldWithSim('w5') w.state.month = 1 void w const ws = w.state.worldSim as { calamity?: string; calamityLeft?: number } // 强制触发:首月+高概率 seed 抽——健壮断言:任何时刻 calamityLeft ∈ [0,6] for (let i = 0; i < 24; i++) { w.advanceMonth() const left = ws.calamityLeft ?? 0 expect(left).toBeLessThanOrEqual(6) if (ws.calamity) expect(left).toBeGreaterThan(0) } }) it('NPC 关系网填充:同风格亲近、异风格有隙,且年首漂移', () => { const w = worldWithSim('w6') const dyn = (w.state.worldSim as { npcDyn: Record }> }).npcDyn const xuan = dyn['n-xuanying']?.relationsWithOthers ?? {} expect(Object.keys(xuan).length).toBeGreaterThanOrEqual(3) const after = (w.state.worldSim as { npcDyn: Record }> }).npcDyn // 长跑后关系网仍存在(不丢) expect(Object.keys(after['n-xuanying']?.relationsWithOthers ?? {}).length).toBeGreaterThanOrEqual(3) }) it('功法价格与 techniquePrice 一致(面板引用安全)', () => { const w = worldWithSim('w7') const t = w.state.family.techniques[0] if (!t) return expect(techniquePrice(t)).toBeGreaterThan(0) expect(buyTechnique(w, t, techniquePrice(t))).toBe(false) // 已录 }) it('无 sim 世界市场价正常(兜底不崩)', () => { const w = World.create({ seed: 'w8', surname: '石', familyName: '石家', motto: 'm', difficulty: 'normal' }) w.state.worldSim = undefined as never expect(marketPrice(w, 'lingcao')).toBeGreaterThan(0) }) })