import { describe, expect, it } from 'vitest' import { World } from '../src/renderer/game/engine/runtime/World' import { GameFacade } from '../src/renderer/game/engine/runtime/ApiFacade' import { SYSTEM_DEFS } from '../src/renderer/game/engine/runtime/capabilities' import { PACK, pack, DEFAULT_PACK } from '../src/renderer/game/data/registry' import { marketPrice } from '../src/renderer/game/engine/sim/Market' import { stateFingerprint, longRun } from './fingerprint.helper' import { resetSaveBus, attachLogSink } from './world.helpers' function baseWorld(seed: string): World { const w = World.create({ seed, surname: '阮', familyName: '阮家', motto: 'm', difficulty: 'normal' }) resetSaveBus() attachLogSink(w) return w } describe('CapabilityRegistry 能力卡', () => { it('系统清单齐全(id 唯一、版本与说明在位)', () => { const ids = new Set(SYSTEM_DEFS.map((d) => d.id)) expect(ids.size).toBe(SYSTEM_DEFS.length) expect(SYSTEM_DEFS.length).toBeGreaterThanOrEqual(11) for (const d of SYSTEM_DEFS) { expect(d.version).toMatch(/^\d+\.\d+$/) expect(d.name.length).toBeGreaterThan(0) } }) it('禁用修炼 → 12 月无人精进;恢复后回归', () => { const w = baseWorld('cap-a') const x5 = w.state.members['x5'] x5.realm = { major: 'qi', minor: 1 } const p0 = x5.realmProgress w.toggleSystem('cultivation') for (let i = 0; i < 12; i++) w.advanceMonth() expect(x5.realmProgress).toBe(p0) w.toggleSystem('cultivation') for (let i = 0; i < 12; i++) w.advanceMonth() expect(x5.realmProgress).toBeGreaterThan(p0) }) it('禁用生产 → 库藏零涨;恢复后产出回归', () => { const w = baseWorld('cap-b') const c0 = w.state.family.inventory['lingcao'] ?? 0 w.toggleSystem('production') for (let i = 0; i < 6; i++) w.advanceMonth() expect(w.state.family.inventory['lingcao'] ?? 0).toBeLessThanOrEqual(c0) w.toggleSystem('production') for (let i = 0; i < 6; i++) w.advanceMonth() expect(w.state.family.inventory['lingcao'] ?? 0).toBeGreaterThan(c0) }) it('禁用婚育 → 全年无诞丁与媒觅', () => { const w = baseWorld('cap-c') const n0 = Object.values(w.state.members).filter((c) => c.bornYear > 1).length w.toggleSystem('marriage') for (let i = 0; i < 48; i++) w.advanceMonth() const n1 = Object.values(w.state.members).filter((c) => c.bornYear > 1).length expect(n1).toBe(n0) }) it('禁用事件 → 迟迟无事,天空澄澈', () => { const w = baseWorld('cap-d') w.toggleSystem('events') for (let i = 0; i < 40; i++) w.advanceMonth() expect(w.state.pendingEvent).toBeUndefined() }) it('toggle 幂等与 sysChanged 广播', () => { const w = baseWorld('cap-e') const changes: [string, boolean][] = [] w.out.push({ onLog: () => undefined, onChronicle: () => undefined, onBattle: () => undefined, onPendingEvent: () => undefined, onGameOver: () => undefined, onSystemChange: (id, enabled) => changes.push([id, enabled]) }) const r1 = w.toggleSystem('season') expect(r1).toBe(false) expect(changes).toEqual([['season', false]]) const r2 = w.toggleSystem('season') expect(r2).toBe(true) expect(w.toggleSystem('no-such-system')).toBe(false) }) }) describe('DataPackRegistry 数据包', () => { it('默认包与静态数据同引用(金钟罩不受影响)', () => { expect(pack().items).toBe(DEFAULT_PACK.items) expect(DEFAULT_PACK.items['lingcao'].name).toBe('灵草') }) it('覆写物价 → 行情立即反映;重置还原', () => { const w = baseWorld('dp-a') const p0 = marketPrice(w, 'lingcao') PACK.override({ items: { ...DEFAULT_PACK.items, lingcao: { ...DEFAULT_PACK.items['lingcao'], basePrice: 999 } } }) const p1 = marketPrice(w, 'lingcao') expect(p1).toBeGreaterThan(p0) PACK.reset() const p2 = marketPrice(w, 'lingcao') expect(p2).toBe(p0) }) it('fingerprint 稳定且对覆写敏感', () => { const f0 = PACK.fingerprint() PACK.override({}) const f1 = PACK.fingerprint() expect(f1).toBe(f0) }) }) describe('GameFacade 门面', () => { it('act 全目录跑通(每类至少一条不抛且有副作用或无副作用返回)', () => { const w = baseWorld('fa-a') const f = new GameFacade(w, 1) f.act('head.set', { memberId: 'x3' }) expect(w.state.family.headId).toBe('x3') f.act('member.post', { memberId: 'x4', post: 'guardian' }) expect(w.state.members['x4'].post).toBe('guardian') f.act('estate.build', { building: 'fangshi' }) expect(w.state.family.buildings['fangshi']).toBe(1) f.act('estate.rite', {}) expect(w.state.family.flag['lastRiteYear']).toBe(1) const afterRite = w.state.family.stones f.act('market.sell', { item: 'lingcao', count: 5 }) expect(w.state.family.stones).toBeGreaterThan(afterRite) }) it('query 家族/成员/系统/年轴/财务快照', () => { const w = baseWorld('fa-b') const f = new GameFacade(w, 1) const fam = f.query('family') as { year: number; reputation: number } expect(fam.year).toBe(1) const mem = f.query('members') as { list: { id: string }[] } expect(mem.list.length).toBe(5) const sys = f.query('systems') as { list: { id: string }[] } expect(sys.list.length).toBe(SYSTEM_DEFS.length) const fin = f.query('finance') as { stones: number } expect(fin.stones).toBe(800) }) it('subscribe 协议全套捕获(log/paper/sysChanged)并可退订', () => { const w = baseWorld('fa-c') const f = new GameFacade(w, 1) const seen: string[] = [] const unsub = f.subscribe((e) => seen.push(e.type)) for (let i = 0; i < 14; i++) w.advanceMonth() f.act('estate.rite', {}) w.toggleSystem('season') expect(seen).toContain('log') expect(seen).toContain('paper') expect(seen).toContain('sysChanged') unsub() const before = seen.length w.advanceMonth() expect(seen.length).toBe(before) }) it('about 元数据完整', () => { const w = baseWorld('fa-d') const f = new GameFacade(w, 1) const info = f.about() expect(info.title).toBe('仙途家族志') expect(info.version).toContain('0.1.30') expect(info.modules).toBeGreaterThanOrEqual(11) expect(info.systems).toBeGreaterThan(0) expect(info.plugins).toBeGreaterThanOrEqual(3) }) it('默认配置金钟罩不受门面化影响', () => { PACK.reset() expect(stateFingerprint(longRun('bell-seed-1').state)).toBe('5670331a') expect(stateFingerprint(longRun('bell-seed-3').state)).toBe('bb5fd1e2') }) })