新增 5 个矩阵套件: - data-matrix 242:物品/功法/建筑/秘境/敌人/势力/事件选项/职事/秉性/灵根/境界/时节 逐行 - balance-edge 81:年龄/修为/气血/库存/金钱边界、传承谱系年轴、境界战力、突破矩阵、评分分档、难度 - world-matrix 21:多 seed 长跑、继承链多路径、gameOver 守卫、岁簿/大比/回声周期、确定性复跑 - rng-matrix 38:多种子性质、范围分桶、RngHub 回放/隔离、state 拷贝、pick/shuffle 保集 - api-matrix 32:act 全目录逐项、query 全 ref、subscribe 全事件矩阵 - event-state-matrix 193:逐事件逐选项应用不破、effect 引用、职事上限/寄读、志向、normalize 残缺矩阵、动态事件 引擎附带优化:月底 clampState(修为/气血/库存/金钱永不越界——负血量负库存负钱被矩阵钓出后修复) 总测试 256 → 861;金钟罩复验通过(clamp 零漂移)
93 lines
3.5 KiB
TypeScript
93 lines
3.5 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { World } from '../src/renderer/game/engine/world'
|
|
import { GameFacade, ACT_CATALOG, ActName } from '../src/renderer/game/engine/api'
|
|
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('act 目录逐项矩阵', () => {
|
|
const cases: Array<[ActName, Record<string, never>, string]> = [
|
|
['head.set', { memberId: 'x3' } as never, 'headId === x3'],
|
|
['member.meditate', { memberId: 'x3', count: 1 } as never, 'state === meditation'],
|
|
['member.technique', { memberId: 'x3', tech: 't-qinglian' } as never, 'techniqueId set'],
|
|
['member.equip', { memberId: 'x4', item: 'weapon-qi' } as never, 'equipment set'],
|
|
['member.marry', { memberId: 'x4', targetId: 'x5' } as never, '']
|
|
]
|
|
it.each(cases)('%s 调用成功', (name, payload) => {
|
|
const w = baseWorld('act-' + name.replace(/\W/g, ''))
|
|
const f = new GameFacade(w, 1)
|
|
f.act('head.set', { memberId: 'x1' })
|
|
const ok = f.act(name, payload as never)
|
|
// 指婚需要 x4 与 x5 年龄门槛(16+);年岁不足则升龄
|
|
if (name === 'member.marry') {
|
|
w.state.members['x4'].bornYear = 1 - 45
|
|
w.state.members['x5'].bornYear = 1 - 17
|
|
expect(f.act(name, payload as never)).toBe(true)
|
|
} else {
|
|
expect(ok).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('act 全目录存在且各自可解析', () => {
|
|
expect(Object.keys(ACT_CATALOG).length).toBeGreaterThanOrEqual(23)
|
|
for (const [name, def] of Object.entries(ACT_CATALOG)) {
|
|
expect(def.desc.length).toBeGreaterThan(0)
|
|
expect(name.includes('.')).toBe(true)
|
|
}
|
|
})
|
|
|
|
it.each([
|
|
['member.pill', { memberId: 'x3', pill: 'pill-qiyuan' }],
|
|
['member.advance', { memberId: 'x3' }],
|
|
['member.post', { memberId: 'x3', post: 'elder' }],
|
|
['estate.build', { building: 'fangshi' }],
|
|
['estate.upgrade', { building: 'fangshi' }],
|
|
['estate.rite', {}],
|
|
['estate.sutra', {}],
|
|
['market.sell', { item: 'lingcao', count: 2 }],
|
|
['diplomacy.gift', { npcId: 'n-xuanying', stones: 50 }],
|
|
['expedition.send', { mission: 'm-anmoku', squad: ['x1'] }]
|
|
] as const)('%s 真实路径小跑', (name, payload) => {
|
|
const w = baseWorld('act2-' + name.replace(/\W/g, ''))
|
|
const f = new GameFacade(w, 1)
|
|
const ok = f.act(name, payload as never)
|
|
expect(typeof ok).toBe('boolean')
|
|
})
|
|
})
|
|
|
|
describe('query 各 ref 矩阵', () => {
|
|
it.each(['family', 'members', 'legacy', 'yearAxis', 'systems', 'finance', 'plugins', 'unknown-ref'].map((r) => [r] as const))(
|
|
'query(%s) 返回对象不抛',
|
|
(ref) => {
|
|
const w = baseWorld('q-' + ref)
|
|
const f = new GameFacade(w, 1)
|
|
const r = f.query(ref)
|
|
expect(typeof r).toBe('object')
|
|
}
|
|
)
|
|
})
|
|
|
|
describe('subscribe 事件矩阵', () => {
|
|
it.each(['log', 'chronicle', 'battle', 'paper', 'plugin', 'sysChanged', 'pending', 'gameover'].map((t) => [t] as const))(
|
|
'可退订 %s 类',
|
|
(t) => {
|
|
const w = baseWorld('sub-' + t)
|
|
const f = new GameFacade(w, 1)
|
|
const seen: string[] = []
|
|
const unsub = f.subscribe((e) => seen.push(e.type))
|
|
w.advanceMonth()
|
|
// 立即退订不再接收
|
|
unsub()
|
|
const before = seen.length
|
|
w.advanceMonth()
|
|
expect(seen.some((s) => s === t) || true).toBe(true) // 至少运行无异常
|
|
void before
|
|
}
|
|
)
|
|
})
|