Files
ChronicleOfTheImmortalClan/tests/facade-registry.test.ts
T
thzxx be6f2f5fe7 v0.1.20: 万世无谬(第二轮全量审计歼灭,1011 测试+双金钟罩)
【事件闸(机制重锤)】
- fire(w,id,priority):单闸+优先级——高优(raid/渡劫/大比)顶替被占者、被顶替入 eventQueue 下月兑现不丢失;普通被占返回 false 调用方冷却(raid 被吞也写 CD)
- pending 唯一写入入口:渡劫/破境丹改走 fire(防覆盖丢弃);名宿传薪年锚 flag(防同年重复 12 次)
- applyEventChoice 前置校验 pendingEvent===id(陈旧 Modal 二次结算防线)

【世界模型四修(真·活世界)】
- era 延续档:flow 权重和<1(留白=续任)——盛世能撑 30 年而非固定 25
- 潮汐供给符号修正:灵涨万物丰(原实现反了)+tide 双界 clamp
- driftMarket 弱锚定:rebalance 0.1→0.025——供需/era/灾年真实撬动价格(原五池被锚死±5%)
- 断供/池底单常量 poolFloorPct=0.35(消灭禁买暗雷带)

【知识防线(审计虫洞修复)】
- 指纹扩展:era/stance/prosperity/relationsWithOthers/calamityLeft/pendingEvent 全入 hash
- 第二金钟罩 GOLDEN_RESOLVED:自动 resolve 长跑锁“现实”世界(原指纹锁的只是“事件流冻结”世界)

【性能与存储】
- 存档节流(疾12/常6/缓4,手动即时)+ saveChronicle O(N)→O(1) 单查增量写(2160 月档告别每 tick 数千 OPFS)
- battles≤220/chronicle≤520 截断(stringify 线性膨胀止血);startNewGame/openState 停 timer 摘旧 bus

【杂项】actDirect 对齐 ACT_CATALOG 全 24 项;about 三版本统一 0.1.20;kernel.bus 死总线删;seed 改 RngHub.rollSeed;normalize 补 eraStartYear/overflux/distress;WorldPanel 买卖条件反转修复;音效与视效同闸

【测试】1011 全绿(42 套件):audit-0.1.20 9 例(事件闸/传薪/二次结算/era延续/潮汐符号/断供一致/弱锚定/指纹敏感)
2026-08-23 15:14:28 +08:00

177 lines
6.5 KiB
TypeScript

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.20')
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('3b784f49')
expect(stateFingerprint(longRun('bell-seed-3').state)).toBe('0bf7ec62')
})
})