Files
ChronicleOfTheImmortalClan/tests/gameengine.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

123 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest'
import { GameEngine } from '../src/renderer/game/engine/GameEngine'
import { makeKernel } from '../src/renderer/game/engine/kernel/Kernel'
import { WorldSim } from '../src/renderer/game/engine/sim/WorldSim'
import { WORLDSIM } from '../src/renderer/game/engine/sim/worldsim-data'
describe('GameEngine 引擎门面', () => {
it('构造:内核注入世界 + 门面 + 数据包', () => {
const e = new GameEngine({ seed: 'engine-1' })
expect(e.world).toBeTruthy()
expect(e.facade).toBeTruthy()
expect(e.kernel.clock.subscriptionCount()).toBeGreaterThanOrEqual(10)
expect(e.status()).toBe('running')
})
it('advance 驱动内核时钟(确定单源)', () => {
const e = new GameEngine({ seed: 'engine-2' })
const y0 = e.view().year
const m0 = e.view().month
e.advance()
const crossed = e.view().month === 1 && e.view().year === y0 + 1
const normal = e.view().month === m0 + 1
expect(crossed || normal).toBe(true)
})
it('act/query/subscribe 全链路', () => {
const e = new GameEngine({ seed: 'engine-3' })
const events: string[] = []
const unsub = e.subscribe((ev) => events.push(ev.type))
expect(e.act('member.post', { memberId: 'x3', post: 'elder' })).toBe(true)
e.advance()
// 一次 advance 必然产生内核事件流(log/pending/paper 至少其一)
expect(events.length).toBeGreaterThan(0)
unsub()
const fam = e.query('family') as { year: number }
expect(fam.year).toBeGreaterThanOrEqual(1)
})
it('datapack 覆写生效 + restore 可回放', () => {
const e = new GameEngine({ seed: 'engine-4' })
e.datapack.override({
items: { ...e.datapack.current().items, lingcao: { ...e.datapack.current().items['lingcao'], basePrice: 250 } }
})
const snap = e.snapshot()
expect(snap.family.stones).toBeGreaterThanOrEqual(0)
e.restore(snap)
expect(e.status()).toBe('running')
})
it('about 元数据含 plugins(≥3)与版本 0.1.20', () => {
const e = new GameEngine({ seed: 'engine-5' })
const a = e.about()
expect(a.plugins).toBeGreaterThanOrEqual(3)
expect(a.version).toContain('0.1.20')
})
})
describe('Kernel 三合一', () => {
it('时钟/随机协同(同 seed 同随机序列);bus 已并入 World.out', () => {
const k1 = makeKernel('kernel-a')
const k2 = makeKernel('kernel-a')
expect(k1.rng.next()).toBe(k2.rng.next())
expect(k1.clock).toBeTruthy()
expect((k1 as unknown as { bus?: unknown }).bus).toBeUndefined()
})
})
function runEngine(e: GameEngine, months: number): void {
for (let i = 0; i < months; i++) {
if (e.view().gameOver) break
e.advance()
// 引擎语义:遇到待决事件停下来等处理;自动化直接取第一个选项继续
let guard = 0
while (e.view().pendingEvent && guard < 4) {
e.resolvePending(0)
guard++
}
}
}
describe('WorldSim 世界健康(长跑)', () => {
it.each(['ws-1', 'ws-2', 'ws-3'].map((s) => [s] as const))('%s:1000 月市场池/灵气在界、世界不崩', (seed) => {
const e = new GameEngine({ seed })
runEngine(e, 1000)
const ws = e.view().worldSim as (Record<string, unknown> & {
marketPool: Record<string, number>
secretQi: Record<string, number>
tide: number
}) | undefined
expect(ws).toBeTruthy()
if (ws) {
for (const v of Object.values(ws.marketPool)) expect(v).toBeGreaterThan(0)
for (const v of Object.values(ws.secretQi ?? {})) expect(v).toBeGreaterThanOrEqual(0)
expect(ws.tide).toBeGreaterThan(0.5)
expect(ws.tide).toBeLessThan(1.4)
}
})
it('NPC 换代至少发生一次(世界在演化)', () => {
const e = new GameEngine({ seed: 'ws-evolve' })
runEngine(e, 1200)
const ws = e.view().worldSim as { npcSuccessions?: number } | undefined
expect(ws?.npcSuccessions ?? 0).toBeGreaterThan(0)
})
it('快讯持有且裁剪符合 N(≤120', () => {
const e = new GameEngine({ seed: 'ws-news' })
runEngine(e, 400)
const ws = e.view().worldSim as { newsFeed?: unknown[] } | undefined
expect(ws?.newsFeed.length ?? 0).toBeLessThanOrEqual(WORLDSIM.newsKeep)
})
})
describe('Sim 与外层联动', () => {
it('tideMult 在界(0.5-1.4)且随年景变化', () => {
const e = new GameEngine({ seed: 'ws-tide-link' })
const sim = new WorldSim(e.world)
const before = sim.tideMult()
expect(before).toBeGreaterThanOrEqual(0.5)
expect(before).toBeLessThan(1.4)
})
})